1use crate::{AccountInfo, Env, SpecName, Test, TransactionParts};
2use revm::{
3 context::{block::BlockEnv, cfg::CfgEnv},
4 database::CacheState,
5 primitives::{hardfork::SpecId, keccak256, Address, Bytes, HashMap, B256},
6 state::Bytecode,
7};
8use serde::Deserialize;
9use std::collections::BTreeMap;
10
11#[derive(Debug, PartialEq, Eq, Deserialize)]
13pub struct TestUnit {
16 #[serde(default, rename = "_info")]
18 pub info: Option<serde_json::Value>,
19
20 pub env: Env,
26
27 pub pre: HashMap<Address, AccountInfo>,
33
34 pub post: BTreeMap<SpecName, Vec<Test>>,
41
42 pub transaction: TransactionParts,
48
49 #[serde(default)]
55 pub out: Option<Bytes>,
56 }
58
59impl TestUnit {
60 pub fn state(&self) -> CacheState {
69 let mut cache_state = CacheState::new(false);
70 for (address, info) in &self.pre {
71 let code_hash = keccak256(&info.code);
72 let bytecode = Bytecode::new_raw_checked(info.code.clone())
73 .unwrap_or(Bytecode::new_legacy(info.code.clone()));
74 let acc_info = revm::state::AccountInfo {
75 balance: info.balance,
76 code_hash,
77 code: Some(bytecode),
78 nonce: info.nonce,
79 };
80 cache_state.insert_account_with_storage(*address, acc_info, info.storage.clone());
81 }
82 cache_state
83 }
84
85 pub fn block_env(&self, cfg: &mut CfgEnv) -> BlockEnv {
98 let mut block = BlockEnv {
99 number: self.env.current_number,
100 beneficiary: self.env.current_coinbase,
101 timestamp: self.env.current_timestamp,
102 gas_limit: self.env.current_gas_limit.try_into().unwrap_or(u64::MAX),
103 basefee: self
104 .env
105 .current_base_fee
106 .unwrap_or_default()
107 .try_into()
108 .unwrap_or(u64::MAX),
109 difficulty: self.env.current_difficulty,
110 prevrandao: self.env.current_random,
111 ..BlockEnv::default()
112 };
113
114 if let Some(current_excess_blob_gas) = self.env.current_excess_blob_gas {
117 block.set_blob_excess_gas_and_price(
118 current_excess_blob_gas.to(),
119 cfg.blob_base_fee_update_fraction(),
120 );
121 }
122
123 if cfg.spec.is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() {
125 block.prevrandao = Some(B256::default());
126 }
127
128 block
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135 use revm::{
136 context_interface::block::calc_blob_gasprice,
137 primitives::{
138 eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
139 U256,
140 },
141 };
142
143 fn create_test_unit_with_excess_blob_gas(excess_blob_gas: u64) -> TestUnit {
145 TestUnit {
146 info: None,
147 env: Env {
148 current_chain_id: None,
149 current_coinbase: Address::ZERO,
150 current_difficulty: U256::ZERO,
151 current_gas_limit: U256::from(1_000_000u64),
152 current_number: U256::from(1u64),
153 current_timestamp: U256::from(1u64),
154 current_base_fee: Some(U256::from(1u64)),
155 previous_hash: None,
156 current_random: None,
157 current_beacon_root: None,
158 current_withdrawals_root: None,
159 current_excess_blob_gas: Some(U256::from(excess_blob_gas)),
160 },
161 pre: HashMap::default(),
162 post: BTreeMap::default(),
163 transaction: TransactionParts {
164 tx_type: None,
165 data: vec![],
166 gas_limit: vec![],
167 gas_price: None,
168 nonce: U256::ZERO,
169 secret_key: B256::ZERO,
170 sender: None,
171 to: None,
172 value: vec![],
173 max_fee_per_gas: None,
174 max_priority_fee_per_gas: None,
175 initcodes: None,
176 access_lists: vec![],
177 authorization_list: None,
178 blob_versioned_hashes: vec![],
179 max_fee_per_blob_gas: None,
180 },
181 out: None,
182 }
183 }
184
185 #[test]
187 fn test_block_env_blob_fee_fraction_cancun() {
188 let unit = create_test_unit_with_excess_blob_gas(0x240000); let mut cfg = CfgEnv::default();
191 cfg.spec = SpecId::CANCUN;
192
193 let block = unit.block_env(&mut cfg);
194
195 let blob_info = block
197 .blob_excess_gas_and_price
198 .expect("blob info should be set");
199 assert_eq!(blob_info.excess_blob_gas, 0x240000);
200
201 let expected_price = calc_blob_gasprice(0x240000, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN);
205 assert_eq!(blob_info.blob_gasprice, expected_price);
206 assert_eq!(blob_info.blob_gasprice, 2); }
208
209 #[test]
211 fn test_block_env_blob_fee_fraction_prague() {
212 let unit = create_test_unit_with_excess_blob_gas(0x240000); let mut cfg = CfgEnv::default();
215 cfg.spec = SpecId::PRAGUE;
216
217 let block = unit.block_env(&mut cfg);
218
219 let blob_info = block
221 .blob_excess_gas_and_price
222 .expect("blob info should be set");
223 assert_eq!(blob_info.excess_blob_gas, 0x240000);
224
225 let expected_price = calc_blob_gasprice(0x240000, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE);
228 assert_eq!(blob_info.blob_gasprice, expected_price);
229 assert_eq!(blob_info.blob_gasprice, 1); }
231
232 #[test]
234 fn test_block_env_blob_fee_fraction_osaka() {
235 let unit = create_test_unit_with_excess_blob_gas(0x240000); let mut cfg = CfgEnv::default();
238 cfg.spec = SpecId::OSAKA;
239
240 let block = unit.block_env(&mut cfg);
241
242 let blob_info = block
244 .blob_excess_gas_and_price
245 .expect("blob info should be set");
246 let expected_price = calc_blob_gasprice(0x240000, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE);
247 assert_eq!(blob_info.blob_gasprice, expected_price);
248 assert_eq!(blob_info.blob_gasprice, 1); }
250
251 #[test]
256 fn test_blob_fee_difference_affects_tx_validity() {
257 let excess_blob_gas = 0x240000u64;
258
259 let cancun_price =
261 calc_blob_gasprice(excess_blob_gas, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN);
262 let prague_price =
263 calc_blob_gasprice(excess_blob_gas, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE);
264
265 assert_eq!(cancun_price, 2, "Cancun blob price should be 2");
267 assert_eq!(prague_price, 1, "Prague blob price should be 1");
268
269 let max_fee_per_blob_gas = 1u128;
273 assert!(
274 max_fee_per_blob_gas < cancun_price,
275 "Tx should fail with Cancun fraction"
276 );
277 assert!(
278 max_fee_per_blob_gas >= prague_price,
279 "Tx should succeed with Prague fraction"
280 );
281 }
282}