1use super::BASE_PRICE_CHANGE_DENOMINATOR as DENOM;
2use cfx_types::U256;
3use log::warn;
4
5pub fn compute_next_price(
6 gas_target: U256, gas_actual: U256, last_base_price: U256,
7 min_base_price: U256,
8) -> U256 {
9 let gas_actual = if gas_actual > gas_target * 2 {
10 if gas_actual > gas_target * 2 + 1 {
12 warn!("gas target is larger than expected");
13 }
14 gas_target * 2
15 } else {
16 gas_actual
17 };
18
19 let next_base_price = if gas_target.is_zero() || gas_target == gas_actual {
20 last_base_price
21 } else {
22 let (gas_delta, delta_sign) = if gas_actual > gas_target {
23 (gas_actual - gas_target, true)
24 } else {
25 (gas_target - gas_actual, false)
26 };
27
28 assert!(gas_delta <= gas_target);
29
30 let mut price_delta = gas_delta * last_base_price / gas_target / DENOM;
31 if price_delta.is_zero() {
32 price_delta = U256::one()
33 }
34
35 if delta_sign {
36 last_base_price + price_delta
37 } else if !last_base_price.is_zero() {
38 last_base_price - price_delta
39 } else {
40 U256::zero()
41 }
42 };
43
44 next_base_price.max(min_base_price)
45}
46
47pub fn estimate_max_possible_gas(
48 gas_target: U256, current_base_price: U256, last_base_price: U256,
49) -> U256 {
50 let (_, upper_boundary) = estimate_gas_used_boundary(
51 gas_target,
52 current_base_price,
53 last_base_price,
54 );
55 match upper_boundary {
56 None => gas_target * 2,
57 Some(U256([0, 0, 0, 0])) => U256::zero(),
58 Some(x) => x - 1,
59 }
60}
61
62pub fn estimate_gas_used_boundary(
70 gas_target: U256, current_base_price: U256, last_base_price: U256,
71) -> (Option<U256>, Option<U256>) {
72 if gas_target.is_zero() {
73 return (None, Some(1.into()));
74 }
75
76 if last_base_price.is_zero() {
77 return if current_base_price == U256::zero() {
78 (None, Some(gas_target + 1))
79 } else if current_base_price == U256::one() {
80 (Some(gas_target), Some(gas_target * 2 + 1))
81 } else {
82 (Some(gas_target * 2), None)
83 };
84 }
85
86 let max_price_delta = U256::max(U256::one(), last_base_price / DENOM);
87 let upper_base_price = last_base_price + max_price_delta;
88 let lower_base_price = last_base_price - max_price_delta;
89
90 if current_base_price > upper_base_price {
91 (Some(gas_target * 2), None)
92 } else if current_base_price < lower_base_price {
93 (None, Some(U256::zero()))
94 } else if current_base_price == last_base_price {
95 (Some(gas_target - 1), Some(gas_target + 1))
96 } else {
97 let (price_delta, delta_sign) = if current_base_price > last_base_price
98 {
99 (current_base_price - last_base_price, true)
100 } else {
101 (last_base_price - current_base_price, false)
102 };
103
104 assert!(!price_delta.is_zero());
105
106 let lower_bound = if price_delta == U256::one() {
107 U256::zero()
108 } else {
109 (price_delta * gas_target * DENOM - 1) / last_base_price
110 };
111
112 let upper_bound =
113 ((price_delta + 1) * gas_target * DENOM + last_base_price - 1)
114 / last_base_price;
115
116 if delta_sign {
117 (
118 Some(gas_target + lower_bound),
119 Some(U256::min(gas_target + upper_bound, gas_target * 2 + 1)),
120 )
121 } else {
122 (
123 gas_target.checked_sub(upper_bound),
125 Some(gas_target - lower_bound),
126 )
127 }
128 }
129}
130
131pub fn compute_next_price_tuple(x: (U256, U256, U256, U256)) -> U256 {
133 compute_next_price(x.0, x.1, x.2, x.3)
134}
135
136#[cfg(test)]
137mod tests {
138 use crate::block_header::compute_next_price;
139
140 use super::{estimate_gas_used_boundary, DENOM, U256};
141 use itertools::Itertools;
142
143 fn test_boundary(gas_target: usize, last_base_price: usize) {
144 let max_price_delta = usize::max(1, last_base_price / DENOM);
145
146 let start = last_base_price.saturating_sub(max_price_delta);
147 let end = last_base_price.saturating_add(max_price_delta);
148
149 let mut next_start = 0usize;
150
151 for price in start..=end {
152 let (lo, up) = estimate_gas_used_boundary(
153 gas_target.into(),
154 price.into(),
155 last_base_price.into(),
156 );
157 let s = lo.map_or(0, |x| x.as_usize() + 1);
158 let e = up.unwrap().as_usize();
159 assert_eq!(s, next_start);
160 next_start = e;
161 for gas_used in s..e {
162 let next_price = compute_next_price(
163 gas_target.into(),
164 gas_used.into(),
165 last_base_price.into(),
166 U256::zero(),
167 )
168 .as_usize();
169 assert_eq!(next_price, price);
170 }
171 }
172 assert_eq!(next_start, gas_target * 2 + 1);
173
174 if start > 0 {
175 let res = estimate_gas_used_boundary(
176 gas_target.into(),
177 (start - 1).into(),
178 last_base_price.into(),
179 );
180 assert_eq!(res, (None, Some(U256::zero())))
181 }
182
183 let res = estimate_gas_used_boundary(
184 gas_target.into(),
185 (end + 1).into(),
186 last_base_price.into(),
187 );
188 assert_eq!(res, (Some((gas_target * 2).into()), None));
189 }
190
191 #[test]
192 fn test_gas_used_estimation() {
193 let tasks = [1, 2, 3, 4, 5, 8, 10, 991, 1000, 1019, 9949, 10000, 10067]
194 .into_iter()
195 .cartesian_product([
196 0, 1, 2, 3, 4, 5, 8, 10, 991, 1000, 1019, 9949, 10000, 10067,
197 ]);
198
199 for (gas_target, last_base_price) in tasks.into_iter() {
200 test_boundary(gas_target, last_base_price);
201 }
202 }
203}