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
132
133
134
135
136
137
138
//! Provides backends for the gas metering instrumentation
use parity_wasm::elements;

/// Implementation details of the specific method of the gas metering.
#[derive(Clone)]
pub enum GasMeter {
	/// Gas metering with an external function.
	External {
		/// Name of the module to import the gas function from.
		module: &'static str,
		/// Name of the external gas function to be imported.
		function: &'static str,
	},
	/// Gas metering with a local function and a mutable global.
	Internal {
		/// Name of the mutable global to be exported.
		global: &'static str,
		/// Body of the local gas counting function to be injected.
		func_instructions: elements::Instructions,
		/// Cost of the gas function execution.
		cost: u64,
	},
}

use super::Rules;
/// Under the hood part of the gas metering mechanics.
pub trait Backend {
	/// Provides the gas metering implementation details.  
	fn gas_meter<R: Rules>(self, module: &elements::Module, rules: &R) -> GasMeter;
}

/// Gas metering with an external host function.
pub mod host_function {
	use super::{Backend, GasMeter, Rules};
	use parity_wasm::elements::Module;
	/// Injects invocations of the gas charging host function into each metering block.
	pub struct Injector {
		/// The name of the module to import the gas function from.
		module: &'static str,
		/// The name of the gas function to import.
		name: &'static str,
	}

	impl Injector {
		pub fn new(module: &'static str, name: &'static str) -> Self {
			Self { module, name }
		}
	}

	impl Backend for Injector {
		fn gas_meter<R: Rules>(self, _module: &Module, _rules: &R) -> GasMeter {
			GasMeter::External { module: self.module, function: self.name }
		}
	}
}

/// Gas metering with a mutable global.
///
/// # Note
///
/// Not for all execution engines this method gives performance wins compared to using an [external
/// host function](host_function). See benchmarks and size overhead tests for examples of how to
/// make measurements needed to decide which gas metering method is better for your particular case.
///
/// # Warning
///
/// It is not recommended to apply [stack limiter](crate::inject_stack_limiter) instrumentation to a
/// module instrumented with this type of gas metering. This could lead to a massive module size
/// bloat. This is a known issue to be fixed in upcoming versions.
pub mod mutable_global {
	use super::{Backend, GasMeter, Rules};
	use alloc::vec;
	use parity_wasm::elements::{self, Instruction, Module};
	/// Injects a mutable global variable and a local function to the module to track
	/// current gas left.
	///
	/// The function is called in every metering block. In case of falling out of gas, the global is
	/// set to the sentinel value `U64::MAX` and `unreachable` instruction is called. The execution
	/// engine should take care of getting the current global value and setting it back in order to
	/// sync the gas left value during an execution.
	pub struct Injector {
		/// The export name of the gas tracking global.
		pub global_name: &'static str,
	}

	impl Injector {
		pub fn new(global_name: &'static str) -> Self {
			Self { global_name }
		}
	}

	impl Backend for Injector {
		fn gas_meter<R: Rules>(self, module: &Module, rules: &R) -> GasMeter {
			let gas_global_idx = module.globals_space() as u32;

			let func_instructions = vec![
				Instruction::GetGlobal(gas_global_idx),
				Instruction::GetLocal(0),
				Instruction::I64GeU,
				Instruction::If(elements::BlockType::NoResult),
				Instruction::GetGlobal(gas_global_idx),
				Instruction::GetLocal(0),
				Instruction::I64Sub,
				Instruction::SetGlobal(gas_global_idx),
				Instruction::Else,
				// sentinel val u64::MAX
				Instruction::I64Const(-1i64),           // non-charged instruction
				Instruction::SetGlobal(gas_global_idx), // non-charged instruction
				Instruction::Unreachable,               // non-charged instruction
				Instruction::End,
				Instruction::End,
			];

			// calculate gas used for the gas charging func execution itself
			let mut gas_fn_cost = func_instructions.iter().fold(0, |cost, instruction| {
				cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
			});
			// don't charge for the instructions used to fail when out of gas
			let fail_cost = vec![
				Instruction::I64Const(-1i64),           // non-charged instruction
				Instruction::SetGlobal(gas_global_idx), // non-charged instruction
				Instruction::Unreachable,               // non-charged instruction
			]
			.iter()
			.fold(0, |cost, instruction| {
				cost + (rules.instruction_cost(instruction).unwrap_or(0) as u64)
			});

			gas_fn_cost -= fail_cost;

			GasMeter::Internal {
				global: self.global_name,
				func_instructions: elements::Instructions::new(func_instructions),
				cost: gas_fn_cost,
			}
		}
	}
}