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    pub 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        if let Some(auth_list) = tx.authorization_list() {
203            excludes
204                .extend(auth_list.iter().filter_map(|auth| auth.authority()));
205        }
206
207        let builtins = match tx.space() {
208            Space::Native => &self.machine.builtins(),
209            Space::Ethereum => &self.machine.builtins_evm(),
210        };
211        excludes.extend(builtins.iter().map(|(addr, _)| *addr));
212
213        let access_list = tx
214            .access_list()
215            .map(|val| val.to_vec())
216            .unwrap_or(AccessList::new());
217
218        Some(AccessListInspector::new(access_list, excludes))
219    }
220
221    fn check_cip130(
222        &self, tx: &SignedTransaction, request: &EstimateRequest,
223    ) -> Option<(ExecutionOutcome, EstimateExt)> {
224        let min_gas_limit = U256::from(tx.data().len() * 100);
225        if !request.has_gas_limit || *tx.gas_limit() >= min_gas_limit {
226            return None;
227        }
228
229        let outcome = ExecutionOutcome::NotExecutedDrop(
230            cfx_executor::executive::TxDropError::NotEnoughGasLimit {
231                expected: min_gas_limit,
232                got: *tx.gas_limit(),
233            },
234        );
235        let estimation = Default::default();
236
237        Some((outcome, estimation))
238    }
239
240    // For the same transaction, the storage limit paid by user and the
241    // storage limit paid by the sponsor are different values. So
242    // this function will
243    //
244    // 1. First Pass: Assuming the sender pays for storage collateral,
245    // check if the transaction will finished
246    //
247    // 2. Second Pass: If it does, executes the transaction again assuming
248    // the sponsor pays for the storage collateral. The resultant
249    // storage limit must be larger than the maximum storage limit
250    // can be afford by the sponsor, to guarantee the user pays for
251    // the storage limit.
252    fn two_pass_estimation(
253        &mut self, tx: &SignedTransaction, request: EstimateRequest,
254    ) -> DbResult<Result<(Executed, Option<u64>, AccessList), ExecutionOutcome>>
255    {
256        let access_list_inspector =
257            self.prepare_access_list_inspector(tx, &request);
258        // First pass
259        let saved = self.state.save();
260        let sender_pay_executed = match self
261            .as_executive()
262            .transact(&tx, request.first_pass_options(access_list_inspector))?
263        {
264            ExecutionOutcome::Finished(executed) => executed,
265            res => {
266                return Ok(Err(res));
267            }
268        };
269        debug!(
270            "Transaction estimate first pass outcome {:?}",
271            sender_pay_executed
272        );
273        self.state.update_state_post_tx_execution(false);
274        self.state.restore(saved);
275
276        let access_list = sender_pay_executed
277            .ext_result
278            .get::<AccessListKey>()
279            .map(|a| a.to_vec())
280            .unwrap_or(AccessList::new());
281
282        // Second pass
283        let contract_pay_executed: Option<Executed>;
284        let collateral_sponsored_contract_if_eligible_sender = self
285            .sponsored_contract_if_eligible_sender(
286                &tx,
287                SponsoredType::Collateral,
288            )?;
289
290        let contract_pay_executed =
291            if collateral_sponsored_contract_if_eligible_sender.is_some() {
292                let saved = self.state.save();
293                let res = self
294                    .as_executive()
295                    .transact(&tx, request.second_pass_options())?;
296                self.state.restore(saved);
297
298                contract_pay_executed = match res {
299                    ExecutionOutcome::Finished(executed) => Some(executed),
300                    res => {
301                        warn!("Should unreachable because two pass estimations should have the same output. \
302                                 Now we have: first pass success {:?}, second pass fail {:?}", sender_pay_executed, res);
303                        None
304                    }
305                };
306                debug!(
307                    "Transaction estimate second pass outcome {:?}",
308                    contract_pay_executed
309                );
310                contract_pay_executed
311            } else {
312                return Ok(Ok((sender_pay_executed, None, access_list)));
313            };
314
315        let contract_address =
316            collateral_sponsored_contract_if_eligible_sender.unwrap();
317
318        let sponsor_balance_for_collateral = self
319            .state
320            .sponsor_balance_for_collateral(&contract_address)?
321            + self
322                .state
323                .available_storage_points_for_collateral(&contract_address)?;
324        let max_sponsor_storage_limit = (sponsor_balance_for_collateral
325            / *DRIPS_PER_STORAGE_COLLATERAL_UNIT)
326            .as_u64();
327        // In some cases we need to overwrite the storage limit to overcome
328        // sponsor_balance_for_collateral.
329        let overwrite_storage_limit = max(
330            storage_limit(&sender_pay_executed),
331            max_sponsor_storage_limit + 64,
332        );
333
334        if let Some(contract_pay_executed) = contract_pay_executed {
335            if max_sponsor_storage_limit
336                >= storage_limit(&contract_pay_executed)
337            {
338                return Ok(Ok((contract_pay_executed, None, access_list)));
339            }
340        };
341
342        Ok(Ok((
343            sender_pay_executed,
344            Some(overwrite_storage_limit),
345            access_list,
346        )))
347    }
348
349    fn enact_executed_by_estimation_request(
350        &self, tx: SignedTransaction, mut executed: Executed,
351        overwrite_storage_limit: Option<u64>, request: &EstimateRequest,
352        access_list: AccessList,
353    ) -> DbResult<(ExecutionOutcome, EstimateExt)> {
354        let estimated_storage_limit =
355            overwrite_storage_limit.unwrap_or(storage_limit(&executed));
356        let estimated_gas_limit = estimated_gas_limit(&executed, &tx);
357        let estimation = EstimateExt {
358            estimated_storage_limit,
359            estimated_gas_limit,
360            access_list,
361        };
362
363        let gas_sponsored_contract_if_eligible_sender = self
364            .sponsored_contract_if_eligible_sender(&tx, SponsoredType::Gas)?;
365
366        if gas_sponsored_contract_if_eligible_sender.is_none()
367            && executed.gas_sponsor_paid
368            && request.has_gas_price
369        {
370            executed.gas_sponsor_paid = false;
371        }
372
373        if !request.has_gas_limit {
374            executed.gas_used = estimated_gas_limit;
375            executed.gas_charged = estimated_gas_limit;
376            executed.fee = estimated_gas_limit.mul(tx.gas_price());
377        }
378
379        // If we don't charge gas, recheck the current gas_fee is ok for
380        // sponsorship.
381        if !request.charge_gas()
382            && request.has_gas_price
383            && executed.gas_sponsor_paid
384        {
385            let contract = gas_sponsored_contract_if_eligible_sender.unwrap();
386            let enough_balance = executed.fee
387                <= self.state.sponsor_balance_for_gas(&contract)?;
388            let enough_bound =
389                executed.fee <= self.state.sponsor_gas_bound(&contract)?;
390            if !(enough_balance && enough_bound) {
391                debug!("Transaction estimate unset \"sponsor_paid\" because of not enough sponsor balance / gas bound.");
392                executed.gas_sponsor_paid = false;
393            }
394        }
395
396        // If the request has a sender, recheck the balance requirement matched.
397        if request.has_sender {
398            // Unwrap safety: in given TransactOptions, this value must be
399            // `Some(_)`.
400            let gas_fee =
401                if request.recheck_gas_fee() && !executed.gas_sponsor_paid {
402                    estimated_gas_limit.saturating_mul(*tx.gas_price())
403                } else {
404                    0.into()
405                };
406            let storage_collateral = if !executed.storage_sponsor_paid {
407                U256::from(estimated_storage_limit)
408                    * *DRIPS_PER_STORAGE_COLLATERAL_UNIT
409            } else {
410                0.into()
411            };
412            let value_and_fee = tx
413                .value()
414                .saturating_add(gas_fee)
415                .saturating_add(storage_collateral);
416            let balance = self.state.balance(&tx.sender())?;
417            if balance < value_and_fee {
418                return Ok((
419                    ExecutionOutcome::ExecutionErrorBumpNonce(
420                        ExecutionError::NotEnoughCash {
421                            required: value_and_fee.into(),
422                            got: balance.into(),
423                            actual_gas_cost: min(balance, gas_fee),
424                            max_storage_limit_cost: storage_collateral,
425                        },
426                        executed,
427                    ),
428                    estimation,
429                ));
430            }
431        }
432
433        if request.has_storage_limit {
434            let storage_limit = tx.storage_limit().unwrap();
435            if storage_limit < estimated_storage_limit {
436                return Ok((
437                    ExecutionOutcome::ExecutionErrorBumpNonce(
438                        ExecutionError::VmError(vm::Error::ExceedStorageLimit),
439                        executed,
440                    ),
441                    estimation,
442                ));
443            }
444        }
445
446        // Revise the gas used in result, if we estimate the transaction with a
447        // default large enough gas.
448        if !request.has_gas_limit {
449            executed.gas_charged = max(
450                estimated_gas_limit - estimated_gas_limit / 4,
451                executed.gas_used,
452            );
453            executed.fee = executed.gas_charged.saturating_mul(*tx.gas_price());
454        }
455
456        return Ok((ExecutionOutcome::Finished(executed), estimation));
457    }
458}
459
460fn estimated_gas_limit(executed: &Executed, tx: &SignedTransaction) -> U256 {
461    #[cfg(not(feature = "align_evm"))]
462    let cip130_min_gas_limit = U256::from(tx.data().len() * 100);
463    #[cfg(feature = "align_evm")]
464    let cip130_min_gas_limit = U256::zero();
465
466    let eip7623_gas_limit = 21000
467        + tx.data()
468            .iter()
469            .map(|&x| if x == 0 { 10 } else { 40 })
470            .sum::<u64>();
471    let estimated =
472        executed.ext_result.get::<GasLimitEstimationKey>().unwrap() * 7 / 6
473            + executed.base_gas;
474    U256::max(
475        eip7623_gas_limit.into(),
476        U256::max(estimated, cip130_min_gas_limit),
477    )
478}
479
480fn storage_limit(executed: &Executed) -> u64 {
481    executed
482        .storage_collateralized
483        .first()
484        .map_or(0, |x| x.collaterals.as_u64())
485}
486
487pub fn decode_error<F, Addr: Display>(
488    executed: &Executed, format_address: F,
489) -> (String, String, Vec<String>)
490where F: Fn(&Address) -> Addr {
491    // When a revert exception happens, there is usually an error in
492    // the sub-calls. So we return the trace
493    // information for debugging contract.
494    let errors = ErrorUnwind::from_executed(executed)
495        .errors
496        .iter()
497        .map(|(addr, error)| format!("{}: {}", format_address(addr), error))
498        .collect::<Vec<String>>();
499
500    // Decode revert error
501    let revert_error = string_revert_reason_decode(&executed.output);
502    let revert_error = if !revert_error.is_empty() {
503        format!(": {}", revert_error)
504    } else {
505        format!("")
506    };
507
508    // Try to fetch the innermost error.
509    let innermost_error = if errors.len() > 0 {
510        format!(" Innermost error is at {}.", errors[0])
511    } else {
512        String::default()
513    };
514    (revert_error, innermost_error, errors)
515}
516
517#[derive(Debug, Clone, Copy)]
518pub struct EstimateRequest {
519    pub has_sender: bool,
520    pub has_gas_limit: bool,
521    pub has_gas_price: bool,
522    pub has_nonce: bool,
523    pub has_storage_limit: bool,
524    pub collect_access_list: bool,
525}
526
527impl EstimateRequest {
528    fn recheck_gas_fee(&self) -> bool { self.has_sender && self.has_gas_price }
529
530    fn charge_gas(&self) -> bool {
531        self.has_sender && self.has_gas_limit && self.has_gas_price
532    }
533
534    fn transact_settings(
535        self, charge_collateral: ChargeCollateral,
536    ) -> TransactSettings {
537        TransactSettings {
538            charge_collateral,
539            charge_gas: self.charge_gas(),
540            check_epoch_bound: false,
541            check_base_price: self.has_gas_price,
542            forbid_eoa_with_code: false,
543        }
544    }
545
546    fn first_pass_options(
547        self, access_list_inspector: Option<AccessListInspector>,
548    ) -> TransactOptions<Observer> {
549        let mut observer = Observer::virtual_call();
550        observer.access_list_inspector = access_list_inspector;
551        TransactOptions {
552            observer,
553            settings: self.transact_settings(ChargeCollateral::EstimateSender),
554        }
555    }
556
557    pub fn second_pass_options(self) -> TransactOptions<Observer> {
558        TransactOptions {
559            observer: Observer::virtual_call(),
560            settings: self.transact_settings(ChargeCollateral::EstimateSponsor),
561        }
562    }
563}