|
| 1 | +use alloy::primitives::{utils::parse_ether, U256}; |
| 2 | +use std::time::Duration; |
| 3 | + |
| 4 | +// We assume a fixed gas cost of 300,000 for each of the 2 transactions |
| 5 | +const ON_CHAIN_COST_IN_GAS_UNITS: u64 = 600_000u64; |
| 6 | + |
| 7 | +/// Decides whether to send the aggregated proof to be verified on-chain based on |
| 8 | +/// time elapsed since last submission and monthly ETH budget. |
| 9 | +/// We make a linear function with the eth to spend this month and the time elapsed since last submission. |
| 10 | +/// If eth to spend / elapsed time is over the linear function, we skip the submission. |
| 11 | +pub fn should_send_proof_to_verify_on_chain( |
| 12 | + time_elapsed: Duration, |
| 13 | + monthly_eth_budget: f64, |
| 14 | + network_gas_price: U256, |
| 15 | +) -> bool { |
| 16 | + let on_chain_cost_in_gas: U256 = U256::from(ON_CHAIN_COST_IN_GAS_UNITS); |
| 17 | + let max_to_spend_wei = max_to_spend_in_wei(time_elapsed, monthly_eth_budget); |
| 18 | + |
| 19 | + let expected_cost_in_wei = network_gas_price * on_chain_cost_in_gas; |
| 20 | + |
| 21 | + expected_cost_in_wei <= max_to_spend_wei |
| 22 | +} |
| 23 | + |
| 24 | +fn max_to_spend_in_wei(time_elapsed: Duration, monthly_eth_budget: f64) -> U256 { |
| 25 | + const SECONDS_PER_MONTH: u64 = 30 * 24 * 60 * 60; |
| 26 | + |
| 27 | + // Note: this expect is safe because in case it was invalid, should have been caught at startup |
| 28 | + let monthly_budget_in_wei = parse_ether(&monthly_eth_budget.to_string()) |
| 29 | + .expect("The monthly budget should be a non-negative value"); |
| 30 | + |
| 31 | + let elapsed_seconds = U256::from(time_elapsed.as_secs()); |
| 32 | + |
| 33 | + let budget_available_per_second_in_wei = monthly_budget_in_wei / U256::from(SECONDS_PER_MONTH); |
| 34 | + |
| 35 | + budget_available_per_second_in_wei * elapsed_seconds |
| 36 | +} |
| 37 | + |
| 38 | +#[cfg(test)] |
| 39 | +mod tests { |
| 40 | + use super::should_send_proof_to_verify_on_chain; |
| 41 | + use alloy::primitives::U256; |
| 42 | + use std::time::Duration; |
| 43 | + |
| 44 | + #[test] |
| 45 | + fn test_should_send_proof_to_verify_on_chain_updated_cases() { |
| 46 | + // The should_send_proof_to_verify_on_chain function returns true when: |
| 47 | + // gas_price * 600_000 <= (seconds_elapsed) * (monthly_eth_budget / (30 * 24 * 60 * 60)) |
| 48 | + |
| 49 | + const BUDGET_PER_MONTH_IN_ETH: f64 = 0.15; |
| 50 | + const ONE_DAY_SECONDS: u64 = 24 * 60 * 60; |
| 51 | + let gas_price = U256::from(1_000_000_000u64); // 1 Gwei |
| 52 | + |
| 53 | + // Case 1: Base case -> should return true |
| 54 | + // Monthly Budget: 0.15 ETH -> 0.005 ETH per day -> 0.000000058 ETH per hour |
| 55 | + // Elapsed Time: 24 hours |
| 56 | + // Gas Price: 1 Gwei |
| 57 | + // Max to spend: 0.000000058 ETH/hour * 24 hours = 0.005 ETH |
| 58 | + // Expected cost: 600,000 * 1 Gwei = 0.0006 ETH |
| 59 | + // Expected cost < Max to spend, so we can send the proof |
| 60 | + assert!(should_send_proof_to_verify_on_chain( |
| 61 | + Duration::from_secs(ONE_DAY_SECONDS), // 24 hours |
| 62 | + BUDGET_PER_MONTH_IN_ETH, // 0.15 ETH monthly budget |
| 63 | + gas_price, // 1 Gwei gas price |
| 64 | + )); |
| 65 | + |
| 66 | + // Case 2: Slightly Increased Gas Price -> should return true |
| 67 | + // Monthly Budget: 0.15 ETH -> 0.005 ETH per day -> 0.000000058 ETH per hour |
| 68 | + // Elapsed Time: 24 hours |
| 69 | + // Gas Price: 8 Gwei |
| 70 | + // Max to spend: 0.000000058 ETH/hour * 24 hours = 0.005 ETH |
| 71 | + // Expected cost: 600,000 * 8 Gwei = 0.0048 ETH |
| 72 | + // Expected cost < Max to spend, so we can send the proof |
| 73 | + assert!(should_send_proof_to_verify_on_chain( |
| 74 | + Duration::from_secs(ONE_DAY_SECONDS), // 24 hours |
| 75 | + BUDGET_PER_MONTH_IN_ETH, // 0.15 ETH monthly budget |
| 76 | + U256::from(8_000_000_000u64), // 8 Gwei gas price |
| 77 | + )); |
| 78 | + |
| 79 | + // Case 3: Increased Gas Price -> should return false |
| 80 | + // Monthly Budget: 0.15 ETH -> 0.005 ETH per day -> 0.000000058 ETH per hour |
| 81 | + // Elapsed Time: 24 hours |
| 82 | + // Gas Price: 10 Gwei |
| 83 | + // Max to spend: 0.000000058 ETH/hour * 24 hours = 0.005 ETH |
| 84 | + // Expected cost: 600,000 * 10 Gwei = 0.006 ETH |
| 85 | + // Expected cost > Max to spend, so we cannot send the proof |
| 86 | + assert!(!should_send_proof_to_verify_on_chain( |
| 87 | + Duration::from_secs(ONE_DAY_SECONDS), // 24 hours |
| 88 | + BUDGET_PER_MONTH_IN_ETH, // 0.15 ETH monthly budget |
| 89 | + U256::from(10_000_000_000u64), // 10 Gwei gas price |
| 90 | + )); |
| 91 | + |
| 92 | + // Case 4: Slightly Reduced Time Elapsed -> should return true |
| 93 | + // Monthly Budget: 0.15 ETH -> 0.005 ETH per day -> 0.000000058 ETH per hour |
| 94 | + // Elapsed Time: 3 hours |
| 95 | + // Gas Price: 1 Gwei |
| 96 | + // Max to spend: 0.000000058 ETH/hour * 3 hours = 0.000625 ETH |
| 97 | + // Expected cost: 600,000 * 1 Gwei = 0.0006 ETH |
| 98 | + // Expected cost < Max to spend, so we can send the proof |
| 99 | + assert!(should_send_proof_to_verify_on_chain( |
| 100 | + Duration::from_secs(3 * 3600), // 3 hours |
| 101 | + BUDGET_PER_MONTH_IN_ETH, // 0.15 ETH monthly budget |
| 102 | + gas_price, // 1 Gwei gas price |
| 103 | + )); |
| 104 | + |
| 105 | + // Case 5: Reduced Time Elapsed -> should return false |
| 106 | + // Monthly Budget: 0.15 ETH -> 0.005 ETH per day -> 0.000000058 ETH per hour |
| 107 | + // Elapsed Time: 1.2 hours |
| 108 | + // Gas Price: 1 Gwei |
| 109 | + // Max to spend: 0.000000058 ETH/hour * 1.2 hours = 0.00025 ETH |
| 110 | + // Expected cost: 600,000 * 1 Gwei = 0.0006 ETH |
| 111 | + // Expected cost > Max to spend, so we cannot send the proof |
| 112 | + assert!(!should_send_proof_to_verify_on_chain( |
| 113 | + Duration::from_secs_f64(1.2 * 3600.0), // 1.2 hours |
| 114 | + BUDGET_PER_MONTH_IN_ETH, // 0.15 ETH monthly budget |
| 115 | + gas_price, // 1 Gwei gas price |
| 116 | + )); |
| 117 | + |
| 118 | + // Case 6: Slightly Reduced Monthly Budget -> should return true |
| 119 | + // Monthly Budget: 0.1 ETH -> 0.0033 ETH per day -> 0.000000038 ETH per hour |
| 120 | + // Elapsed Time: 24 hours |
| 121 | + // Gas Price: 1 Gwei |
| 122 | + // Max to spend: 0.000000038 ETH/hour * 24 hours = 0.0032832 ETH |
| 123 | + // Expected cost: 600,000 * 1 Gwei = 0.0006 ETH |
| 124 | + // Expected cost < Max to spend, so we can send the proof |
| 125 | + assert!(should_send_proof_to_verify_on_chain( |
| 126 | + Duration::from_secs(ONE_DAY_SECONDS), // 24 hours |
| 127 | + 0.1, // 0.1 ETH monthly budget |
| 128 | + gas_price, // 1 Gwei gas price |
| 129 | + )); |
| 130 | + |
| 131 | + // Case 7: Decreased Monthly Budget -> should return false |
| 132 | + // Monthly Budget: 0.01 ETH -> 0.00033 ETH per day -> 0.0000000038 ETH per hour |
| 133 | + // Elapsed Time: 24 hours |
| 134 | + // Gas Price: 1 Gwei |
| 135 | + // Max to spend: 0.0000000038 ETH/hour * 24 hours = 0.00032832 ETH |
| 136 | + // Expected cost: 600,000 * 1 Gwei = 0.0006 ETH |
| 137 | + // Expected cost > Max to spend, so we cannot send the proof |
| 138 | + assert!(!should_send_proof_to_verify_on_chain( |
| 139 | + Duration::from_secs(ONE_DAY_SECONDS), // 24 hours |
| 140 | + 0.01, // 0.01 ETH monthly budget |
| 141 | + gas_price, // 1 Gwei gas price |
| 142 | + )); |
| 143 | + } |
| 144 | +} |
0 commit comments