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