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