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, }
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 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 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 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 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 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 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 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 !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 request.has_sender {
422 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 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 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 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 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}