1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use cfx_executor::{
    observer::{
        CallTracer, CheckpointTracer, DrainTrace, InternalTransferTracer,
        OpcodeTracer, StorageTracer,
    },
    stack::FrameResult,
};
use cfx_parameters::{
    block::CROSS_SPACE_GAS_RATIO,
    internal_contract_addresses::CROSS_SPACE_CONTRACT_ADDRESS,
};
use cfx_types::U256;
use cfx_vm_types::ActionParams;

use typemap::ShareDebugMap;

const EVM_RATIO: (u64, u64) = (64, 63);
const CROSS_SPACE_RATIO: (u64, u64) = (CROSS_SPACE_GAS_RATIO, 1);

struct FrameGasInfo {
    init_gas: U256,
    gas_cost_in_subcall: U256,
    gas_limit_for_subcall: U256,
    cross_space_internal: bool,
}

impl FrameGasInfo {
    #[inline]
    fn gas_cost(&self, gas_left: &U256) -> U256 {
        // Due to gas stipend, the gas_left could be larger than gas cost.
        self.init_gas.saturating_sub(*gas_left)
    }

    #[inline]
    fn gas_cost_this_level(&self, gas_left: &U256) -> U256 {
        self.gas_cost(gas_left)
            .saturating_sub(self.gas_cost_in_subcall)
    }

    #[inline]
    fn minimum_init_gas(&self, gas_left: &U256, ratio: (u64, u64)) -> U256 {
        let (numerator, denominator) = ratio;
        self.gas_cost_this_level(gas_left)
            + (self.gas_limit_for_subcall * numerator + denominator - 1)
                / denominator
    }
}

#[derive(Default)]
pub struct GasMan {
    gas_limit: U256,
    gas_record: Vec<FrameGasInfo>,
}

impl DrainTrace for GasMan {
    fn drain_trace(self, map: &mut ShareDebugMap) {
        map.insert::<GasLimitEstimation>(self.gas_required());
    }
}

pub struct GasLimitEstimation;

impl typemap::Key for GasLimitEstimation {
    type Value = U256;
}

impl GasMan {
    pub fn gas_required(&self) -> U256 { self.gas_limit }

    fn record_call_create(
        &mut self, gas_pass_in: &U256, cross_space_internal: bool,
    ) {
        self.gas_record.push(FrameGasInfo {
            init_gas: gas_pass_in.clone(),
            gas_cost_in_subcall: U256::zero(),
            gas_limit_for_subcall: U256::zero(),
            cross_space_internal,
        })
    }

    fn record_return(&mut self, gas_left: &U256) {
        let child_level = self.gas_record.pop().unwrap();
        let ratio = if child_level.cross_space_internal {
            CROSS_SPACE_RATIO
        } else {
            EVM_RATIO
        };

        if let Some(FrameGasInfo {
            gas_cost_in_subcall,
            gas_limit_for_subcall,
            ..
        }) = self.gas_record.last_mut()
        {
            *gas_cost_in_subcall += child_level.gas_cost(gas_left);
            *gas_limit_for_subcall +=
                child_level.minimum_init_gas(gas_left, ratio);
        } else {
            self.gas_limit = child_level.minimum_init_gas(gas_left, ratio);
        }
    }
}

impl CallTracer for GasMan {
    fn record_call(&mut self, params: &ActionParams) {
        let cross_space_internal =
            params.code_address == CROSS_SPACE_CONTRACT_ADDRESS;
        self.record_call_create(&params.gas, cross_space_internal);
    }

    fn record_call_result(&mut self, result: &FrameResult) {
        let gas_left =
            result.as_ref().map_or(U256::zero(), |r| r.gas_left.clone());
        self.record_return(&gas_left);
    }

    fn record_create(&mut self, params: &ActionParams) {
        self.record_call_create(&params.gas, false);
    }

    fn record_create_result(&mut self, result: &FrameResult) {
        let gas_left =
            result.as_ref().map_or(U256::zero(), |r| r.gas_left.clone());
        self.record_return(&gas_left);
    }
}

impl CheckpointTracer for GasMan {}
impl InternalTransferTracer for GasMan {}
impl StorageTracer for GasMan {}
impl OpcodeTracer for GasMan {}