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