primitives/block_header/
base_price.rs

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        // This case may happen if block_gas_limit is an odd number.
11        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
62// Returns the outside boundary gas usage values that define the range
63//
64// The first item represents the maximum value that the next_price <
65// current_base_price.
66//
67// The second item represents the minimum value that the
68// next_price > current_base_price.
69pub 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                // Underflow could happen
124                gas_target.checked_sub(upper_bound),
125                Some(gas_target - lower_bound),
126            )
127        }
128    }
129}
130
131/// A helper function for `compute_next_price` which takes a typle as input
132pub 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}