1use super::{
2 execution_outcome::{ExecutionOutcome, ToRepackError, TxDropError},
3 gas_required_for,
4 transact_options::{ChargeCollateral, TransactOptions, TransactSettings},
5 ExecutiveContext, PreCheckedExecutive,
6};
7use crate::{
8 executive::eip7623_required_gas, executive_observer::ExecutiveObserver,
9 substate::Substate,
10};
11use cfx_parameters::staking::DRIPS_PER_STORAGE_COLLATERAL_UNIT;
12
13use cfx_statedb::Result as DbResult;
14use cfx_types::{Address, AddressSpaceUtil, Space, U256, U512};
15use primitives::{
16 extract_7702_payload, transaction::Action, SignedTransaction, Transaction,
17};
18
19macro_rules! early_return_on_err {
20 ($e:expr) => {
21 match $e {
22 Ok(x) => x,
23 Err(exec_outcom) => {
24 return Ok(Err(exec_outcom));
25 }
26 }
27 };
28}
29
30pub struct FreshExecutive<'a, O: ExecutiveObserver> {
31 context: ExecutiveContext<'a>,
32 tx: &'a SignedTransaction,
33 observer: O,
34 settings: TransactSettings,
35}
36
37pub(super) struct CostInfo {
38 pub sender_balance: U512,
40 pub base_gas: u64,
43 pub floor_gas: u64,
45
46 pub total_cost: U512,
48 pub gas_cost: U512,
50 pub storage_cost: U256,
52 pub sender_intended_cost: U512,
55 pub gas_price: U256,
57 pub burnt_gas_price: U256,
59
60 pub gas_sponsored: bool,
62 pub storage_sponsored: bool,
64 pub storage_sponsor_eligible: bool,
66}
67
68impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> {
69 pub fn new(
70 context: ExecutiveContext<'a>, tx: &'a SignedTransaction,
71 options: TransactOptions<O>,
72 ) -> Self {
73 let TransactOptions {
74 observer, settings, ..
75 } = options;
76 FreshExecutive {
77 context,
78 tx,
79 observer,
80 settings,
81 }
82 }
83
84 pub(super) fn check_all(
85 self,
86 ) -> DbResult<Result<PreCheckedExecutive<'a, O>, ExecutionOutcome>> {
87 early_return_on_err!(self.check_base_price());
88 early_return_on_err!(self.check_nonce()?);
90
91 if self.context.spec.cip152 && self.settings.forbid_eoa_with_code {
92 early_return_on_err!(self.check_from_eoa_with_code()?);
93 }
94
95 if self.settings.check_epoch_bound {
97 early_return_on_err!(self.check_epoch_bound()?);
98 }
99
100 let cost = early_return_on_err!(self.compute_cost_info()?);
101
102 if self.context.spec.align_evm {
103 early_return_on_err!(self.check_enough_balance(&cost));
104 }
105
106 early_return_on_err!(self.check_sender_exist(&cost)?);
107
108 Ok(Ok(self.into_pre_checked(cost)))
109 }
110
111 fn into_pre_checked(self, cost: CostInfo) -> PreCheckedExecutive<'a, O> {
112 PreCheckedExecutive {
113 context: self.context,
114 tx: self.tx,
115 observer: self.observer,
116 settings: self.settings,
117 cost,
118 substate: Substate::new(),
119 }
120 }
121}
122
123impl<'a, O: ExecutiveObserver> FreshExecutive<'a, O> {
124 fn check_nonce(&self) -> DbResult<Result<(), ExecutionOutcome>> {
125 let tx = self.tx;
126 let nonce = self.context.state.nonce(&tx.sender())?;
127 Ok(if *tx.nonce() < nonce {
128 Err(ExecutionOutcome::NotExecutedDrop(TxDropError::OldNonce(
129 nonce,
130 *tx.nonce(),
131 )))
132 } else if *tx.nonce() > nonce {
133 Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
134 ToRepackError::InvalidNonce {
135 expected: nonce,
136 got: *tx.nonce(),
137 },
138 ))
139 } else {
140 Ok(())
141 })
142 }
143
144 fn check_from_eoa_with_code(
145 &self,
146 ) -> DbResult<Result<(), ExecutionOutcome>> {
147 let sender = self.tx.sender();
148 let Some(code) = self.context.state.code(&sender)? else {
149 return Ok(Ok(()));
151 };
152
153 if code.is_empty() {
154 return Ok(Ok(()));
156 }
157
158 if self.tx.space() == Space::Ethereum
159 && extract_7702_payload(&code).is_some()
160 {
161 return Ok(Ok(()));
163 }
164
165 Ok(Err(ExecutionOutcome::NotExecutedDrop(
167 TxDropError::SenderWithCode(sender.address),
168 )))
169 }
170
171 fn check_base_price(&self) -> Result<(), ExecutionOutcome> {
172 if !self.settings.check_base_price {
173 return Ok(());
174 }
175
176 let burnt_gas_price = self.context.env.burnt_gas_price[self.tx.space()];
177 if self.tx.gas_price() < &burnt_gas_price {
178 Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
179 ToRepackError::NotEnoughBaseFee {
180 expected: burnt_gas_price,
181 got: *self.tx.gas_price(),
182 },
183 ))
184 } else {
185 Ok(())
186 }
187 }
188
189 fn check_epoch_bound(&self) -> DbResult<Result<(), ExecutionOutcome>> {
190 let tx = if let Transaction::Native(ref tx) =
191 self.tx.transaction.transaction.unsigned
192 {
193 tx
194 } else {
195 return Ok(Ok(()));
196 };
197
198 let env = self.context.env;
199
200 if tx.epoch_height().abs_diff(env.epoch_height)
201 > env.transaction_epoch_bound
202 {
203 Ok(Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
204 ToRepackError::EpochHeightOutOfBound {
205 block_height: env.epoch_height,
206 set: *tx.epoch_height(),
207 transaction_epoch_bound: env.transaction_epoch_bound,
208 },
209 )))
210 } else {
211 Ok(Ok(()))
212 }
213 }
214
215 fn check_sender_exist(
216 &self, cost: &CostInfo,
217 ) -> DbResult<Result<(), ExecutionOutcome>> {
218 if !cost.sender_intended_cost.is_zero()
219 && !self.context.state.exists(&self.tx.sender())?
220 {
221 return Ok(Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
225 ToRepackError::SenderDoesNotExist,
226 )));
227 }
228 Ok(Ok(()))
229 }
230
231 fn check_enough_balance(
236 &self, cost: &CostInfo,
237 ) -> Result<(), ExecutionOutcome> {
238 if cost.sender_balance < cost.sender_intended_cost {
239 Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
240 ToRepackError::NotEnoughBalance {
241 expected: cost.sender_intended_cost,
242 got: cost.sender_balance.try_into().unwrap(),
243 },
244 ))
245 } else {
246 Ok(())
247 }
248 }
249
250 fn compute_cost_info(
251 &self,
252 ) -> DbResult<Result<CostInfo, ExecutionOutcome>> {
253 let tx = self.tx;
254 let settings = self.settings;
255 let sender = tx.sender();
256 let state = &self.context.state;
257 let env = self.context.env;
258 let spec = self.context.spec;
259
260 let base_gas = gas_required_for(
261 tx.action() == Action::Create,
262 &tx.data(),
263 tx.access_list(),
264 tx.authorization_len(),
265 &spec.to_consensus_spec(),
266 );
267
268 let floor_gas =
269 eip7623_required_gas(&tx.data(), &spec.to_consensus_spec());
270
271 let minimum_tx_gas = u64::max(base_gas, floor_gas);
272
273 if *tx.gas() < minimum_tx_gas.into() {
274 return Ok(Err(ExecutionOutcome::NotExecutedDrop(
275 TxDropError::NotEnoughGasLimit {
276 expected: minimum_tx_gas.into(),
277 got: *tx.gas(),
278 },
279 )));
280 }
281
282 let check_base_price = self.settings.check_base_price;
283
284 let gas_price = if !spec.cip1559 || !check_base_price {
285 *tx.gas_price()
286 } else {
287 let actual_base_gas =
289 U256::min(*tx.gas_price(), env.base_gas_price[tx.space()]);
290 tx.effective_gas_price(&actual_base_gas)
291 };
292 let max_gas_price = *tx.gas_price();
293
294 let burnt_gas_price = env.burnt_gas_price[tx.space()];
295 assert!(gas_price >= burnt_gas_price || !check_base_price);
299
300 let sender_balance = U512::from(state.balance(&sender)?);
301 let gas_cost = if settings.charge_gas {
302 tx.gas().full_mul(gas_price)
303 } else {
304 0.into()
305 };
306
307 let additional_gas_required_1559 =
309 if settings.charge_gas && spec.cip645.fix_eip1559 {
310 (max_gas_price - gas_price).full_mul(*tx.gas_limit())
311 } else {
312 0.into()
313 };
314 let storage_cost =
315 if let (Transaction::Native(tx), ChargeCollateral::Normal) = (
316 &tx.transaction.transaction.unsigned,
317 settings.charge_collateral,
318 ) {
319 U256::from(*tx.storage_limit())
320 * *DRIPS_PER_STORAGE_COLLATERAL_UNIT
321 } else {
322 U256::zero()
323 };
324
325 if sender.space == Space::Ethereum {
326 assert_eq!(storage_cost, U256::zero());
327 let sender_cost = U512::from(tx.value()) + gas_cost;
328 let sender_intended_cost =
329 sender_cost + additional_gas_required_1559;
330 return Ok(Ok(CostInfo {
331 sender_balance,
332 base_gas,
333 floor_gas,
334 gas_cost,
335 gas_price,
336 burnt_gas_price,
337 storage_cost,
338 sender_intended_cost,
339 total_cost: sender_cost,
340 gas_sponsored: false,
341 storage_sponsored: false,
342 storage_sponsor_eligible: false,
343 }));
344 }
345
346 let mut code_address = Address::zero();
348 let mut gas_sponsor_eligible = false;
349 let mut storage_sponsor_eligible = false;
350
351 if let Action::Call(ref address) = tx.action() {
352 if !spec.is_valid_address(address) {
353 return Ok(Err(ExecutionOutcome::NotExecutedDrop(
354 TxDropError::InvalidRecipientAddress(*address),
355 )));
356 }
357 if state.is_contract_with_code(&address.with_native_space())? {
358 code_address = *address;
359 if state
360 .check_contract_whitelist(&code_address, &sender.address)?
361 {
362 gas_sponsor_eligible = gas_cost
364 + additional_gas_required_1559
365 <= U512::from(state.sponsor_gas_bound(&code_address)?);
366 storage_sponsor_eligible =
367 state.sponsor_for_collateral(&code_address)?.is_some();
368 }
369 }
370 }
371
372 let code_address = code_address;
373 let gas_sponsor_eligible = gas_sponsor_eligible;
374 let storage_sponsor_eligible = storage_sponsor_eligible;
375
376 let sponsor_balance_for_gas =
378 U512::from(state.sponsor_balance_for_gas(&code_address)?);
379 let gas_sponsored =
380 gas_sponsor_eligible && sponsor_balance_for_gas >= gas_cost;
381
382 let sponsor_balance_for_storage = state
383 .sponsor_balance_for_collateral(&code_address)?
384 + state.available_storage_points_for_collateral(&code_address)?;
385 let storage_sponsored = match settings.charge_collateral {
386 ChargeCollateral::Normal => {
387 storage_sponsor_eligible
388 && storage_cost <= sponsor_balance_for_storage
389 }
390 ChargeCollateral::EstimateSender => false,
391 ChargeCollateral::EstimateSponsor => true,
392 };
393
394 let sender_intended_cost = {
395 let mut sender_intended_cost = U512::from(tx.value());
396
397 if !gas_sponsor_eligible {
398 sender_intended_cost += gas_cost + additional_gas_required_1559;
399 }
400 if !storage_sponsor_eligible {
401 sender_intended_cost += storage_cost.into();
402 }
403 sender_intended_cost
404 };
405 let total_cost = {
406 let mut total_cost = U512::from(tx.value());
407 if !gas_sponsored {
408 total_cost += gas_cost
409 }
410 if !storage_sponsored {
411 total_cost += storage_cost.into();
412 }
413 total_cost
414 };
415 if sender_balance >= sender_intended_cost && sender_balance < total_cost
422 {
423 let gas_sponsor_balance = if gas_sponsor_eligible {
424 sponsor_balance_for_gas
425 } else {
426 0.into()
427 };
428
429 let storage_sponsor_balance = if storage_sponsor_eligible {
430 sponsor_balance_for_storage
431 } else {
432 0.into()
433 };
434
435 return Ok(Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
436 ToRepackError::NotEnoughCashFromSponsor {
437 required_gas_cost: gas_cost,
438 gas_sponsor_balance,
439 required_storage_cost: storage_cost,
440 storage_sponsor_balance,
441 },
442 )));
443 }
444
445 return Ok(Ok(CostInfo {
446 sender_intended_cost,
447 base_gas,
448 floor_gas,
449 gas_cost,
450 gas_price,
451 burnt_gas_price,
452 storage_cost,
453 sender_balance,
454 total_cost,
455 gas_sponsored,
456 storage_sponsored,
457 storage_sponsor_eligible,
461 }));
462 }
463}