reth_optimism_chainspec/
basefee.rs1use core::cmp::max;
4
5use alloy_consensus::BlockHeader;
6use alloy_eips::calc_next_block_base_fee;
7use op_alloy_consensus::{decode_holocene_extra_data, decode_jovian_extra_data, EIP1559ParamError};
8use reth_chainspec::{BaseFeeParams, EthChainSpec};
9use reth_optimism_forks::OpHardforks;
10
11pub fn decode_holocene_base_fee<H>(
17 chain_spec: impl EthChainSpec + OpHardforks,
18 parent: &H,
19 timestamp: u64,
20) -> Result<u64, EIP1559ParamError>
21where
22 H: BlockHeader,
23{
24 let (elasticity, denominator) = decode_holocene_extra_data(parent.extra_data())?;
25
26 let base_fee_params = if elasticity == 0 && denominator == 0 {
27 chain_spec.base_fee_params_at_timestamp(timestamp)
28 } else {
29 BaseFeeParams::new(denominator as u128, elasticity as u128)
30 };
31
32 Ok(parent.next_block_base_fee(base_fee_params).unwrap_or_default())
33}
34
35pub fn compute_jovian_base_fee<H>(
44 chain_spec: impl EthChainSpec + OpHardforks,
45 parent: &H,
46 timestamp: u64,
47) -> Result<u64, EIP1559ParamError>
48where
49 H: BlockHeader,
50{
51 let (elasticity, denominator, min_base_fee) = decode_jovian_extra_data(parent.extra_data())?;
52
53 let base_fee_params = if elasticity == 0 && denominator == 0 {
54 chain_spec.base_fee_params_at_timestamp(timestamp)
55 } else {
56 BaseFeeParams::new(denominator as u128, elasticity as u128)
57 };
58
59 let gas_used = max(parent.gas_used(), parent.blob_gas_used().unwrap_or_default());
62
63 let next_base_fee = calc_next_block_base_fee(
64 gas_used,
65 parent.gas_limit(),
66 parent.base_fee_per_gas().unwrap_or_default(),
67 base_fee_params,
68 );
69
70 if next_base_fee < min_base_fee {
71 return Ok(min_base_fee);
72 }
73
74 Ok(next_base_fee)
75}
76
77#[cfg(test)]
78mod tests {
79 use alloc::sync::Arc;
80
81 use op_alloy_consensus::encode_jovian_extra_data;
82 use reth_chainspec::{ChainSpec, ForkCondition, Hardfork};
83 use reth_optimism_forks::OpHardfork;
84
85 use crate::{OpChainSpec, BASE_SEPOLIA};
86
87 use super::*;
88
89 const JOVIAN_TIMESTAMP: u64 = 1900000000;
90
91 fn get_chainspec() -> Arc<OpChainSpec> {
92 let mut base_sepolia_spec = BASE_SEPOLIA.inner.clone();
93 base_sepolia_spec
94 .hardforks
95 .insert(OpHardfork::Jovian.boxed(), ForkCondition::Timestamp(JOVIAN_TIMESTAMP));
96 Arc::new(OpChainSpec {
97 inner: ChainSpec {
98 chain: base_sepolia_spec.chain,
99 genesis: base_sepolia_spec.genesis,
100 genesis_header: base_sepolia_spec.genesis_header,
101 ..Default::default()
102 },
103 })
104 }
105
106 #[test]
107 fn test_next_base_fee_jovian_blob_gas_used_greater_than_gas_used() {
108 let chain_spec = get_chainspec();
109 let mut parent = chain_spec.genesis_header().clone();
110 let timestamp = JOVIAN_TIMESTAMP;
111
112 const GAS_LIMIT: u64 = 10_000_000_000;
113 const BLOB_GAS_USED: u64 = 5_000_000_000;
114 const GAS_USED: u64 = 1_000_000_000;
115 const MIN_BASE_FEE: u64 = 100_000_000;
116
117 parent.extra_data =
118 encode_jovian_extra_data([0; 8].into(), BaseFeeParams::base_sepolia(), MIN_BASE_FEE)
119 .unwrap();
120 parent.blob_gas_used = Some(BLOB_GAS_USED);
121 parent.gas_used = GAS_USED;
122 parent.gas_limit = GAS_LIMIT;
123
124 let expected_base_fee = calc_next_block_base_fee(
125 BLOB_GAS_USED,
126 parent.gas_limit(),
127 parent.base_fee_per_gas().unwrap_or_default(),
128 BaseFeeParams::base_sepolia(),
129 );
130 assert_eq!(
131 expected_base_fee,
132 compute_jovian_base_fee(chain_spec, &parent, timestamp).unwrap()
133 );
134 assert_ne!(
135 expected_base_fee,
136 calc_next_block_base_fee(
137 GAS_USED,
138 parent.gas_limit(),
139 parent.base_fee_per_gas().unwrap_or_default(),
140 BaseFeeParams::base_sepolia(),
141 )
142 )
143 }
144
145 #[test]
146 fn test_next_base_fee_jovian_blob_gas_used_less_than_gas_used() {
147 let chain_spec = get_chainspec();
148 let mut parent = chain_spec.genesis_header().clone();
149 let timestamp = JOVIAN_TIMESTAMP;
150
151 const GAS_LIMIT: u64 = 10_000_000_000;
152 const BLOB_GAS_USED: u64 = 100_000_000;
153 const GAS_USED: u64 = 1_000_000_000;
154 const MIN_BASE_FEE: u64 = 100_000_000;
155
156 parent.extra_data =
157 encode_jovian_extra_data([0; 8].into(), BaseFeeParams::base_sepolia(), MIN_BASE_FEE)
158 .unwrap();
159 parent.blob_gas_used = Some(BLOB_GAS_USED);
160 parent.gas_used = GAS_USED;
161 parent.gas_limit = GAS_LIMIT;
162
163 let expected_base_fee = calc_next_block_base_fee(
164 GAS_USED,
165 parent.gas_limit(),
166 parent.base_fee_per_gas().unwrap_or_default(),
167 BaseFeeParams::base_sepolia(),
168 );
169 assert_eq!(
170 expected_base_fee,
171 compute_jovian_base_fee(chain_spec, &parent, timestamp).unwrap()
172 );
173 }
174
175 #[test]
176 fn test_next_base_fee_jovian_min_base_fee() {
177 let chain_spec = get_chainspec();
178 let mut parent = chain_spec.genesis_header().clone();
179 let timestamp = JOVIAN_TIMESTAMP;
180
181 const GAS_LIMIT: u64 = 10_000_000_000;
182 const BLOB_GAS_USED: u64 = 100_000_000;
183 const GAS_USED: u64 = 1_000_000_000;
184 const MIN_BASE_FEE: u64 = 5_000_000_000;
185
186 parent.extra_data =
187 encode_jovian_extra_data([0; 8].into(), BaseFeeParams::base_sepolia(), MIN_BASE_FEE)
188 .unwrap();
189 parent.blob_gas_used = Some(BLOB_GAS_USED);
190 parent.gas_used = GAS_USED;
191 parent.gas_limit = GAS_LIMIT;
192
193 let expected_base_fee = MIN_BASE_FEE;
194 assert_eq!(
195 expected_base_fee,
196 compute_jovian_base_fee(chain_spec, &parent, timestamp).unwrap()
197 );
198 }
199}