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 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 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 fn two_pass_estimation(
208 &mut self, tx: &SignedTransaction, request: EstimateRequest,
209 ) -> DbResult<Result<(Executed, Option<u64>), ExecutionOutcome>> {
210 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 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 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 !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 request.has_sender {
341 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 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 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 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 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}