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