|
| 1 | +use rand::{Rng, SeedableRng}; |
| 2 | +use rand_chacha::ChaCha8Rng; |
1 | 3 | use starknet_api::block::GasPrice; |
2 | 4 |
|
3 | | -use crate::snip35::{compute_fee_actual, compute_fee_proposal, compute_fee_target}; |
| 5 | +use crate::snip35::{ |
| 6 | + compute_fee_actual, |
| 7 | + compute_fee_proposal, |
| 8 | + compute_fee_target, |
| 9 | + FEE_PROPOSAL_MARGIN_PPT, |
| 10 | + ORACLE_L2_GAS_FLOOR_MAX_FRI, |
| 11 | + ORACLE_L2_GAS_FLOOR_MIN_FRI, |
| 12 | + PPT_DENOMINATOR, |
| 13 | + TARGET_ATTO_USD_PER_L2_GAS, |
| 14 | +}; |
4 | 15 |
|
5 | 16 | #[test] |
6 | 17 | fn test_compute_fee_actual_with_10_identical_values() { |
@@ -184,3 +195,55 @@ fn test_compute_fee_actual_lone_adversary_cannot_skew_median() { |
184 | 195 | window.push(GasPrice(u128::MAX / 2)); |
185 | 196 | assert_eq!(compute_fee_actual(&window, 10), Some(GasPrice(1_000_000))); |
186 | 197 | } |
| 198 | + |
| 199 | +/// The validator's accept predicate. Must stay in sync with |
| 200 | +/// `validate_proposal::is_proposal_init_valid` SNIP-35 bounds check. |
| 201 | +fn validator_accepts(fee_actual: GasPrice, fee_proposal: GasPrice, margin_ppt: u128) -> bool { |
| 202 | + let lower = fee_actual.0.saturating_mul(PPT_DENOMINATOR) / (PPT_DENOMINATOR + margin_ppt); |
| 203 | + let upper = fee_actual.0.saturating_mul(PPT_DENOMINATOR + margin_ppt) / PPT_DENOMINATOR; |
| 204 | + fee_proposal.0 >= lower && fee_proposal.0 <= upper |
| 205 | +} |
| 206 | + |
| 207 | +#[test] |
| 208 | +fn test_malicious_high_fee_proposal_rejected() { |
| 209 | + // Upper bound for fee_actual=1_000_000 with margin=2ppt is 1_002_000. |
| 210 | + let fee_actual = GasPrice(1_000_000); |
| 211 | + assert!(validator_accepts(fee_actual, GasPrice(1_002_000), 2)); |
| 212 | + for proposal in [1_002_001u128, 1_003_000, 2_000_000, u128::MAX] { |
| 213 | + assert!(!validator_accepts(fee_actual, GasPrice(proposal), 2), "accepted {proposal}"); |
| 214 | + } |
| 215 | +} |
| 216 | + |
| 217 | +#[test] |
| 218 | +fn test_malicious_low_fee_proposal_rejected() { |
| 219 | + // Lower bound for fee_actual=1_000_000 with margin=2ppt is 998_003. |
| 220 | + let fee_actual = GasPrice(1_000_000); |
| 221 | + assert!(validator_accepts(fee_actual, GasPrice(998_003), 2)); |
| 222 | + for proposal in [998_002u128, 500_000, 1, 0] { |
| 223 | + assert!(!validator_accepts(fee_actual, GasPrice(proposal), 2), "accepted {proposal}"); |
| 224 | + } |
| 225 | +} |
| 226 | + |
| 227 | +#[test] |
| 228 | +fn test_honest_proposer_always_passes_validation_fuzzed() { |
| 229 | + // Consensus safety: whatever compute_fee_proposal produces, the validator accepts. |
| 230 | + let mut rng = ChaCha8Rng::seed_from_u64(0xDEADBEEF); |
| 231 | + for _ in 0..10_000 { |
| 232 | + let fee_actual_value = rng.gen_range(1u128..1_000_000_000_000_000_000); |
| 233 | + let strk_usd_rate = rng.gen_range(1u128..2 * 10u128.pow(18)); |
| 234 | + let fee_actual = GasPrice(fee_actual_value); |
| 235 | + let target = compute_fee_target( |
| 236 | + TARGET_ATTO_USD_PER_L2_GAS, |
| 237 | + strk_usd_rate, |
| 238 | + ORACLE_L2_GAS_FLOOR_MIN_FRI, |
| 239 | + ORACLE_L2_GAS_FLOOR_MAX_FRI, |
| 240 | + ); |
| 241 | + let oracle_result = if rng.gen_bool(0.1) { None } else { Some(target) }; |
| 242 | + let proposal = compute_fee_proposal(oracle_result, fee_actual, FEE_PROPOSAL_MARGIN_PPT); |
| 243 | + assert!( |
| 244 | + validator_accepts(fee_actual, proposal, FEE_PROPOSAL_MARGIN_PPT), |
| 245 | + "fee_actual={fee_actual_value} rate={strk_usd_rate} proposal={}", |
| 246 | + proposal.0 |
| 247 | + ); |
| 248 | + } |
| 249 | +} |
0 commit comments