1use crate::{AccountInfo, Env, SpecName, Test, TransactionParts};
2use context::{block::BlockEnv, cfg::CfgEnv};
3use database::CacheState;
4use primitives::{hardfork::SpecId, keccak256, AddressMap, Bytes, 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: AddressMap<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 slot_num: self
111 .env
112 .slot_number
113 .unwrap_or_default()
114 .try_into()
115 .unwrap_or(u64::MAX),
116 ..BlockEnv::default()
117 };
118
119 if let Some(current_excess_blob_gas) = self.env.current_excess_blob_gas {
122 block.set_blob_excess_gas_and_price(
123 current_excess_blob_gas.to(),
124 cfg.blob_base_fee_update_fraction(),
125 );
126 }
127
128 if cfg.spec().is_enabled_in(SpecId::MERGE) && block.prevrandao.is_none() {
130 block.prevrandao = Some(B256::default());
131 }
132
133 block
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use context_interface::block::calc_blob_gasprice;
141 use primitives::{
142 eip4844::{BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
143 Address, U256,
144 };
145
146 fn create_test_unit_with_excess_blob_gas(excess_blob_gas: u64) -> TestUnit {
148 TestUnit {
149 info: None,
150 env: Env {
151 current_chain_id: None,
152 current_coinbase: Address::ZERO,
153 current_difficulty: U256::ZERO,
154 current_gas_limit: U256::from(1_000_000u64),
155 current_number: U256::from(1u64),
156 current_timestamp: U256::from(1u64),
157 current_base_fee: Some(U256::from(1u64)),
158 previous_hash: None,
159 current_random: None,
160 current_beacon_root: None,
161 current_withdrawals_root: None,
162 current_excess_blob_gas: Some(U256::from(excess_blob_gas)),
163 slot_number: Some(U256::from(1u64)),
164 },
165 pre: AddressMap::default(),
166 post: BTreeMap::default(),
167 transaction: TransactionParts {
168 tx_type: None,
169 data: vec![],
170 gas_limit: vec![],
171 gas_price: None,
172 nonce: U256::ZERO,
173 secret_key: B256::ZERO,
174 sender: None,
175 to: None,
176 value: vec![],
177 max_fee_per_gas: None,
178 max_priority_fee_per_gas: None,
179 initcodes: None,
180 access_lists: vec![],
181 authorization_list: None,
182 blob_versioned_hashes: vec![],
183 max_fee_per_blob_gas: None,
184 },
185 out: None,
186 }
187 }
188
189 #[test]
191 fn test_block_env_blob_fee_fraction_cancun() {
192 let unit = create_test_unit_with_excess_blob_gas(0x240000); let mut cfg = CfgEnv::new_with_spec(SpecId::CANCUN);
195
196 let block = unit.block_env(&mut cfg);
197
198 let blob_info = block
200 .blob_excess_gas_and_price
201 .expect("blob info should be set");
202 assert_eq!(blob_info.excess_blob_gas, 0x240000);
203
204 let expected_price = calc_blob_gasprice(0x240000, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN);
208 assert_eq!(blob_info.blob_gasprice, expected_price);
209 assert_eq!(blob_info.blob_gasprice, 2); }
211
212 #[test]
214 fn test_block_env_blob_fee_fraction_prague() {
215 let unit = create_test_unit_with_excess_blob_gas(0x240000); let mut cfg = CfgEnv::new_with_spec(SpecId::PRAGUE);
218
219 let block = unit.block_env(&mut cfg);
220
221 let blob_info = block
223 .blob_excess_gas_and_price
224 .expect("blob info should be set");
225 assert_eq!(blob_info.excess_blob_gas, 0x240000);
226
227 let expected_price = calc_blob_gasprice(0x240000, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE);
230 assert_eq!(blob_info.blob_gasprice, expected_price);
231 assert_eq!(blob_info.blob_gasprice, 1); }
233
234 #[test]
236 fn test_block_env_blob_fee_fraction_osaka() {
237 let unit = create_test_unit_with_excess_blob_gas(0x240000); let mut cfg = CfgEnv::new_with_spec(SpecId::OSAKA);
240
241 let block = unit.block_env(&mut cfg);
242
243 let blob_info = block
245 .blob_excess_gas_and_price
246 .expect("blob info should be set");
247 let expected_price = calc_blob_gasprice(0x240000, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE);
248 assert_eq!(blob_info.blob_gasprice, expected_price);
249 assert_eq!(blob_info.blob_gasprice, 1); }
251
252 #[test]
257 fn test_blob_fee_difference_affects_tx_validity() {
258 let excess_blob_gas = 0x240000u64;
259
260 let cancun_price =
262 calc_blob_gasprice(excess_blob_gas, BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN);
263 let prague_price =
264 calc_blob_gasprice(excess_blob_gas, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE);
265
266 assert_eq!(cancun_price, 2, "Cancun blob price should be 2");
268 assert_eq!(prague_price, 1, "Prague blob price should be 1");
269
270 let max_fee_per_blob_gas = 1u128;
274 assert!(
275 max_fee_per_blob_gas < cancun_price,
276 "Tx should fail with Cancun fraction"
277 );
278 assert!(
279 max_fee_per_blob_gas >= prague_price,
280 "Tx should succeed with Prague fraction"
281 );
282 }
283}