cfx_execute_helper/
estimation.rs

1use cfx_executor::{
2    executive::{
3        ChargeCollateral, Executed, ExecutionError, ExecutionOutcome,
4        ExecutiveContext, TransactOptions, TransactSettings,
5    },
6    machine::Machine,
7    state::State,
8};
9use solidity_abi::string_revert_reason_decode;
10
11use super::observer::{
12    access_list::{AccessListInspector, AccessListKey},
13    exec_tracer::ErrorUnwind,
14    gasman::GasLimitEstimationKey,
15    Observer,
16};
17use cfx_parameters::{consensus::ONE_CFX_IN_DRIP, staking::*};
18use cfx_statedb::Result as DbResult;
19use cfx_types::{
20    address_util::AddressUtil, Address, AddressSpaceUtil, Space, U256,
21};
22use cfx_vm_types::{self as vm, Env, Spec};
23use primitives::{
24    transaction::Action, AccessList, SignedTransaction, Transaction,
25};
26use std::{
27    cmp::{max, min},
28    collections::HashSet,
29    fmt::Display,
30    ops::{Mul, Shl},
31};
32
33enum SponsoredType {
34    Gas,
35    Collateral,
36}
37
38#[derive(Default, Debug)]
39pub struct EstimateExt {
40    pub estimated_gas_limit: U256,
41    pub estimated_storage_limit: u64,
42    pub access_list: AccessList, // default empty
43}
44
45pub struct EstimationContext<'a> {
46    state: &'a mut State,
47    env: &'a Env,
48    machine: &'a Machine,
49    spec: &'a Spec,
50}
51
52impl<'a> EstimationContext<'a> {
53    pub fn new(
54        state: &'a mut State, env: &'a Env, machine: &'a Machine,
55        spec: &'a Spec,
56    ) -> Self {
57        EstimationContext {
58            state,
59            env,
60            machine,
61            spec,
62        }
63    }
64
65    fn as_executive<'b>(&'b mut self) -> ExecutiveContext<'b> {
66        ExecutiveContext::new(self.state, self.env, self.machine, self.spec)
67    }
68
69    fn process_estimate_request(
70        &mut self, tx: &mut SignedTransaction, request: &EstimateRequest,
71    ) -> DbResult<()> {
72        if !request.has_sender {
73            let mut random_hex = Address::random();
74            if tx.space() == Space::Native {
75                random_hex.set_user_account_type_bits();
76            }
77            tx.sender = random_hex;
78            tx.public = None;
79
80            // If the sender is not specified, give it enough balance: 1 billion
81            // CFX.
82            let balance_inc = min(
83                tx.value().saturating_add(
84                    U256::from(1_000_000_000) * ONE_CFX_IN_DRIP,
85                ),
86                U256::one().shl(128),
87            );
88
89            self.state.add_balance(
90                &random_hex.with_space(tx.space()),
91                &balance_inc,
92            )?;
93            // Make sure statistics are also correct and will not violate any
94            // underlying assumptions.
95            self.state.add_total_issued(balance_inc);
96            if tx.space() == Space::Ethereum {
97                self.state.add_total_evm_tokens(balance_inc);
98            }
99        }
100
101        if request.has_nonce {
102            self.state.set_nonce(&tx.sender(), &tx.nonce())?;
103        } else {
104            *tx.nonce_mut() = self.state.nonce(&tx.sender())?;
105        }
106
107        Ok(())
108    }
109
110    fn sponsored_contract_if_eligible_sender(
111        &self, tx: &SignedTransaction, ty: SponsoredType,
112    ) -> DbResult<Option<Address>> {
113        if let Transaction::Native(ref native_tx) = tx.unsigned {
114            if let Action::Call(to) = native_tx.action() {
115                if to.is_contract_address() {
116                    let sponsor = match ty {
117                        SponsoredType::Gas => {
118                            self.state.sponsor_for_gas(&to)?
119                        }
120                        SponsoredType::Collateral => {
121                            self.state.sponsor_for_collateral(&to)?
122                        }
123                    };
124                    let has_sponsor = sponsor.map_or(false, |x| !x.is_zero());
125
126                    if has_sponsor
127                        && self.state.check_contract_whitelist(
128                            &to,
129                            &tx.sender().address,
130                        )?
131                    {
132                        return Ok(Some(to));
133                    }
134                }
135            }
136        }
137        Ok(None)
138    }
139
140    pub fn transact_virtual(
141        &mut self, mut tx: SignedTransaction, request: EstimateRequest,
142    ) -> DbResult<(ExecutionOutcome, EstimateExt)> {
143        #[cfg(not(feature = "align_evm"))]
144        if let Some(outcome) = self.check_cip130(&tx, &request) {
145            return Ok(outcome);
146        }
147
148        self.process_estimate_request(&mut tx, &request)?;
149
150        let (executed, overwrite_storage_limit, access_list) = match self
151            .two_pass_estimation(&tx, request)?
152        {
153            Ok(x) => x,
154            Err(execution) => {
155                let estimate_ext = match &execution {
156                    ExecutionOutcome::NotExecutedDrop(_)
157                    | ExecutionOutcome::NotExecutedToReconsiderPacking(_) => {
158                        EstimateExt::default()
159                    }
160                    ExecutionOutcome::ExecutionErrorBumpNonce(_, executed) => {
161                        EstimateExt {
162                            estimated_gas_limit: estimated_gas_limit(
163                                executed, &tx,
164                            ),
165                            estimated_storage_limit: storage_limit(executed),
166                            access_list: AccessList::new(),
167                        }
168                    }
169                    ExecutionOutcome::Finished(_) => unreachable!(),
170                };
171                return Ok((execution, estimate_ext));
172            }
173        };
174
175        self.enact_executed_by_estimation_request(
176            tx,
177            executed,
178            overwrite_storage_limit,
179            &request,
180            access_list,
181        )
182    }
183
184    fn prepare_access_list_inspector(
185        &mut self, tx: &SignedTransaction, request: &EstimateRequest,
186    ) -> Option<AccessListInspector> {
187        if !request.collect_access_list {
188            return None;
189        }
190        // prepare the excludes: add from, to, precompiles, 7702 authorities to
191        // excludes
192        let mut excludes = HashSet::new();
193        excludes.insert(tx.sender().address);
194        let to = match tx.transaction.action() {
195            Action::Call(to) => to,
196            Action::Create => {
197                tx.cal_created_address().expect("should success").address
198            }
199        };
200        excludes.insert(to);
201
202        let chain_id = self
203            .env
204            .chain_id
205            .get(&tx.space())
206            .cloned()
207            .expect("chain id should exist");
208
209        if let Some(auth_list) = tx.authorization_list() {
210            excludes.extend(
211                auth_list
212                    .iter()
213                    .filter(|auth| {
214                        let auth_chain_id = auth.chain_id;
215                        (auth_chain_id.is_zero()
216                            || auth_chain_id == U256::from(chain_id))
217                            && auth.nonce != u64::MAX
218                    })
219                    .filter_map(|auth| auth.authority()),
220            );
221        }
222
223        let builtins = match tx.space() {
224            Space::Native => &self.machine.builtins(),
225            Space::Ethereum => &self.machine.builtins_evm(),
226        };
227
228        excludes.extend(
229            builtins
230                .iter()
231                .filter(|(_, builtin)| {
232                    builtin.is_active(self.env.number, self.env.epoch_height)
233                })
234                .map(|(addr, _)| *addr),
235        );
236
237        let access_list = tx
238            .access_list()
239            .map(|val| val.to_vec())
240            .unwrap_or(AccessList::new());
241
242        Some(AccessListInspector::new(access_list, excludes))
243    }
244
245    fn check_cip130(
246        &self, tx: &SignedTransaction, request: &EstimateRequest,
247    ) -> Option<(ExecutionOutcome, EstimateExt)> {
248        let min_gas_limit = U256::from(tx.data().len() * 100);
249        if !request.has_gas_limit || *tx.gas_limit() >= min_gas_limit {
250            return None;
251        }
252
253        let outcome = ExecutionOutcome::NotExecutedDrop(
254            cfx_executor::executive::TxDropError::NotEnoughGasLimit {
255                expected: min_gas_limit,
256                got: *tx.gas_limit(),
257            },
258        );
259        let estimation = Default::default();
260
261        Some((outcome, estimation))
262    }
263
264    // For the same transaction, the storage limit paid by user and the
265    // storage limit paid by the sponsor are different values. So
266    // this function will
267    //
268    // 1. First Pass: Assuming the sender pays for storage collateral,
269    // check if the transaction will finished
270    //
271    // 2. Second Pass: If it does, executes the transaction again assuming
272    // the sponsor pays for the storage collateral. The resultant
273    // storage limit must be larger than the maximum storage limit
274    // can be afford by the sponsor, to guarantee the user pays for
275    // the storage limit.
276    fn two_pass_estimation(
277        &mut self, tx: &SignedTransaction, request: EstimateRequest,
278    ) -> DbResult<Result<(Executed, Option<u64>, AccessList), ExecutionOutcome>>
279    {
280        let access_list_inspector =
281            self.prepare_access_list_inspector(tx, &request);
282        // First pass
283        let saved = self.state.save();
284        let sender_pay_executed = match self
285            .as_executive()
286            .transact(&tx, request.first_pass_options(access_list_inspector))?
287        {
288            ExecutionOutcome::Finished(executed) => executed,
289            res => {
290                return Ok(Err(res));
291            }
292        };
293        debug!(
294            "Transaction estimate first pass outcome {:?}",
295            sender_pay_executed
296        );
297        self.state.update_state_post_tx_execution(false);
298        self.state.restore(saved);
299
300        let access_list = sender_pay_executed
301            .ext_result
302            .get::<AccessListKey>()
303            .map(|a| a.to_vec())
304            .unwrap_or(AccessList::new());
305
306        // Second pass
307        let contract_pay_executed: Option<Executed>;
308        let collateral_sponsored_contract_if_eligible_sender = self
309            .sponsored_contract_if_eligible_sender(
310                &tx,
311                SponsoredType::Collateral,
312            )?;
313
314        let contract_pay_executed =
315            if collateral_sponsored_contract_if_eligible_sender.is_some() {
316                let saved = self.state.save();
317                let res = self
318                    .as_executive()
319                    .transact(&tx, request.second_pass_options())?;
320                self.state.restore(saved);
321
322                contract_pay_executed = match res {
323                    ExecutionOutcome::Finished(executed) => Some(executed),
324                    res => {
325                        warn!("Should unreachable because two pass estimations should have the same output. \
326                                 Now we have: first pass success {:?}, second pass fail {:?}", sender_pay_executed, res);
327                        None
328                    }
329                };
330                debug!(
331                    "Transaction estimate second pass outcome {:?}",
332                    contract_pay_executed
333                );
334                contract_pay_executed
335            } else {
336                return Ok(Ok((sender_pay_executed, None, access_list)));
337            };
338
339        let contract_address =
340            collateral_sponsored_contract_if_eligible_sender.unwrap();
341
342        let sponsor_balance_for_collateral = self
343            .state
344            .sponsor_balance_for_collateral(&contract_address)?
345            + self
346                .state
347                .available_storage_points_for_collateral(&contract_address)?;
348        let max_sponsor_storage_limit = (sponsor_balance_for_collateral
349            / *DRIPS_PER_STORAGE_COLLATERAL_UNIT)
350            .as_u64();
351        // In some cases we need to overwrite the storage limit to overcome
352        // sponsor_balance_for_collateral.
353        let overwrite_storage_limit = max(
354            storage_limit(&sender_pay_executed),
355            max_sponsor_storage_limit + 64,
356        );
357
358        if let Some(contract_pay_executed) = contract_pay_executed {
359            if max_sponsor_storage_limit
360                >= storage_limit(&contract_pay_executed)
361            {
362                return Ok(Ok((contract_pay_executed, None, access_list)));
363            }
364        };
365
366        Ok(Ok((
367            sender_pay_executed,
368            Some(overwrite_storage_limit),
369            access_list,
370        )))
371    }
372
373    fn enact_executed_by_estimation_request(
374        &self, tx: SignedTransaction, mut executed: Executed,
375        overwrite_storage_limit: Option<u64>, request: &EstimateRequest,
376        access_list: AccessList,
377    ) -> DbResult<(ExecutionOutcome, EstimateExt)> {
378        let estimated_storage_limit =
379            overwrite_storage_limit.unwrap_or(storage_limit(&executed));
380        let estimated_gas_limit = estimated_gas_limit(&executed, &tx);
381        let estimation = EstimateExt {
382            estimated_storage_limit,
383            estimated_gas_limit,
384            access_list,
385        };
386
387        let gas_sponsored_contract_if_eligible_sender = self
388            .sponsored_contract_if_eligible_sender(&tx, SponsoredType::Gas)?;
389
390        if gas_sponsored_contract_if_eligible_sender.is_none()
391            && executed.gas_sponsor_paid
392            && request.has_gas_price
393        {
394            executed.gas_sponsor_paid = false;
395        }
396
397        if !request.has_gas_limit {
398            executed.gas_used = estimated_gas_limit;
399            executed.gas_charged = estimated_gas_limit;
400            executed.fee = estimated_gas_limit.mul(tx.gas_price());
401        }
402
403        // If we don't charge gas, recheck the current gas_fee is ok for
404        // sponsorship.
405        if !request.charge_gas()
406            && request.has_gas_price
407            && executed.gas_sponsor_paid
408        {
409            let contract = gas_sponsored_contract_if_eligible_sender.unwrap();
410            let enough_balance = executed.fee
411                <= self.state.sponsor_balance_for_gas(&contract)?;
412            let enough_bound =
413                executed.fee <= self.state.sponsor_gas_bound(&contract)?;
414            if !(enough_balance && enough_bound) {
415                debug!("Transaction estimate unset \"sponsor_paid\" because of not enough sponsor balance / gas bound.");
416                executed.gas_sponsor_paid = false;
417            }
418        }
419
420        // If the request has a sender, recheck the balance requirement matched.
421        if request.has_sender {
422            // Unwrap safety: in given TransactOptions, this value must be
423            // `Some(_)`.
424            let gas_fee =
425                if request.recheck_gas_fee() && !executed.gas_sponsor_paid {
426                    estimated_gas_limit.saturating_mul(*tx.gas_price())
427                } else {
428                    0.into()
429                };
430            let storage_collateral = if !executed.storage_sponsor_paid {
431                U256::from(estimated_storage_limit)
432                    * *DRIPS_PER_STORAGE_COLLATERAL_UNIT
433            } else {
434                0.into()
435            };
436            let value_and_fee = tx
437                .value()
438                .saturating_add(gas_fee)
439                .saturating_add(storage_collateral);
440            let balance = self.state.balance(&tx.sender())?;
441            if balance < value_and_fee {
442                return Ok((
443                    ExecutionOutcome::ExecutionErrorBumpNonce(
444                        ExecutionError::NotEnoughCash {
445                            required: value_and_fee.into(),
446                            got: balance.into(),
447                            actual_gas_cost: min(balance, gas_fee),
448                            max_storage_limit_cost: storage_collateral,
449                        },
450                        executed,
451                    ),
452                    estimation,
453                ));
454            }
455        }
456
457        if request.has_storage_limit {
458            let storage_limit = tx.storage_limit().unwrap();
459            if storage_limit < estimated_storage_limit {
460                return Ok((
461                    ExecutionOutcome::ExecutionErrorBumpNonce(
462                        ExecutionError::VmError(vm::Error::ExceedStorageLimit),
463                        executed,
464                    ),
465                    estimation,
466                ));
467            }
468        }
469
470        // Revise the gas used in result, if we estimate the transaction with a
471        // default large enough gas.
472        if !request.has_gas_limit {
473            executed.gas_charged = max(
474                estimated_gas_limit - estimated_gas_limit / 4,
475                executed.gas_used,
476            );
477            executed.fee = executed.gas_charged.saturating_mul(*tx.gas_price());
478        }
479
480        return Ok((ExecutionOutcome::Finished(executed), estimation));
481    }
482}
483
484fn estimated_gas_limit(executed: &Executed, tx: &SignedTransaction) -> U256 {
485    #[cfg(not(feature = "align_evm"))]
486    let cip130_min_gas_limit = U256::from(tx.data().len() * 100);
487    #[cfg(feature = "align_evm")]
488    let cip130_min_gas_limit = U256::zero();
489
490    let eip7623_gas_limit = 21000
491        + tx.data()
492            .iter()
493            .map(|&x| if x == 0 { 10 } else { 40 })
494            .sum::<u64>();
495    let estimated =
496        executed.ext_result.get::<GasLimitEstimationKey>().unwrap() * 7 / 6
497            + executed.base_gas;
498    U256::max(
499        eip7623_gas_limit.into(),
500        U256::max(estimated, cip130_min_gas_limit),
501    )
502}
503
504fn storage_limit(executed: &Executed) -> u64 {
505    executed
506        .storage_collateralized
507        .first()
508        .map_or(0, |x| x.collaterals.as_u64())
509}
510
511pub fn decode_error<F, Addr: Display>(
512    executed: &Executed, format_address: F,
513) -> (String, String, Vec<String>)
514where F: Fn(&Address) -> Addr {
515    // When a revert exception happens, there is usually an error in
516    // the sub-calls. So we return the trace
517    // information for debugging contract.
518    let errors = ErrorUnwind::from_executed(executed)
519        .errors
520        .iter()
521        .map(|(addr, error)| format!("{}: {}", format_address(addr), error))
522        .collect::<Vec<String>>();
523
524    // Decode revert error
525    let revert_error = string_revert_reason_decode(&executed.output);
526    let revert_error = if !revert_error.is_empty() {
527        format!(": {}", revert_error)
528    } else {
529        format!("")
530    };
531
532    // Try to fetch the innermost error.
533    let innermost_error = if errors.len() > 0 {
534        format!(" Innermost error is at {}.", errors[0])
535    } else {
536        String::default()
537    };
538    (revert_error, innermost_error, errors)
539}
540
541#[derive(Debug, Clone, Copy)]
542pub struct EstimateRequest {
543    pub has_sender: bool,
544    pub has_gas_limit: bool,
545    pub has_gas_price: bool,
546    pub has_nonce: bool,
547    pub has_storage_limit: bool,
548    pub collect_access_list: bool,
549}
550
551impl EstimateRequest {
552    fn recheck_gas_fee(&self) -> bool { self.has_sender && self.has_gas_price }
553
554    fn charge_gas(&self) -> bool {
555        self.has_sender && self.has_gas_limit && self.has_gas_price
556    }
557
558    fn transact_settings(
559        self, charge_collateral: ChargeCollateral,
560    ) -> TransactSettings {
561        TransactSettings {
562            charge_collateral,
563            charge_gas: self.charge_gas(),
564            check_epoch_bound: false,
565            check_base_price: self.has_gas_price,
566            forbid_eoa_with_code: false,
567        }
568    }
569
570    fn first_pass_options(
571        self, access_list_inspector: Option<AccessListInspector>,
572    ) -> TransactOptions<Observer> {
573        let mut observer = Observer::virtual_call();
574        observer.access_list_inspector = access_list_inspector;
575        TransactOptions {
576            observer,
577            settings: self.transact_settings(ChargeCollateral::EstimateSender),
578        }
579    }
580
581    pub fn second_pass_options(self) -> TransactOptions<Observer> {
582        TransactOptions {
583            observer: Observer::virtual_call(),
584            settings: self.transact_settings(ChargeCollateral::EstimateSponsor),
585        }
586    }
587}