cfx_executor/executive/
fresh_executive.rs

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    /// Sender balance
38    pub sender_balance: U512,
39    /// The intrinsic gas (21000/53000 + tx data gas + access list gas +
40    /// authorization list gas)
41    pub base_gas: u64,
42    /// The floor gas from EIP-7623
43    pub floor_gas: u64,
44
45    /// Transaction value + gas cost (except the sponsored part)
46    pub total_cost: U512,
47    /// Gas cost
48    pub gas_cost: U512,
49    /// Storage collateral cost
50    pub storage_cost: U256,
51    /// Transaction value + gas cost (except the part that eligible for
52    /// sponsor)
53    pub sender_intended_cost: U512,
54    /// Effective gas price
55    pub gas_price: U256,
56    /// Burnt gas price
57    pub burnt_gas_price: U256,
58
59    /// Transaction's gas is sponsored
60    pub gas_sponsored: bool,
61    /// Transaction's collateral is sponsored
62    pub storage_sponsored: bool,
63    /// Transaction's gas is in the sponsor whitelist
64    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        // Validate transaction nonce
88        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        // Validate transaction epoch height.
95        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            // EOA with no code
149            return Ok(Ok(()));
150        };
151
152        if code.is_empty() {
153            // Empty code
154            return Ok(Ok(()));
155        }
156
157        if self.tx.space() == Space::Ethereum
158            && extract_7702_payload(&code).is_some()
159        {
160            // 7702 code in eSpace is allowed
161            return Ok(Ok(()));
162        }
163
164        // extract_7702_payload
165        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            // We don't want to bump nonce for non-existent account when we
221            // can't charge gas fee. In this case, the sender account will
222            // not be created if it does not exist.
223            return Ok(Err(ExecutionOutcome::NotExecutedToReconsiderPacking(
224                ToRepackError::SenderDoesNotExist,
225            )));
226        }
227        Ok(Ok(()))
228    }
229
230    // In the EVM, when the transaction sender's balance is insufficient to
231    // cover the required `gas fee + transfer value`, the transaction does not
232    // bump the nonce or charge a fee, which differs from Conflux. This function
233    // is only used for `align_evm` testing to simulate this EVM behavior.
234    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            // actual_base_gas >= tx gas_price >= burnt_base_price
287            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        // gas_price >= actual_base_gas >=
295        // either 1. tx gas_price >= burnt_gas_price
296        // or     2. base_gas_price >= burnt_gas_price
297        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        // EIP-1559 requires the user balance can afford "max gas price * gas limit", instead of "effective gas price * gas limit", this variable represents "(effective gas price - max gas price) * gas limit"
307        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        // Check if contract will pay transaction fee for the sender.
346        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                    // No need to check for gas sponsor account existence.
362                    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        // Sender pays for gas when sponsor runs out of balance.
376        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        // Sponsor is allowed however sender do not have enough balance to pay
415        // for the extra gas because sponsor has run out of balance in
416        // the mean time.
417        //
418        // Sender is not responsible for the incident, therefore we don't fail
419        // the transaction.
420        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            // Only for backward compatible for a early bug.
457            // The receipt reported `storage_sponsor_eligible` instead of
458            // `storage_sponsored`.
459            storage_sponsor_eligible,
460        }));
461    }
462}