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