referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/
gas_encoder.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17//! Encodes/Decodes EVM gas values.
18
19use crate::Weight;
20use core::ops::{Div, Rem};
21use frame_support::pallet_prelude::CheckedShl;
22use sp_arithmetic::traits::{One, Zero};
23use sp_core::U256;
24
25// We use 3 digits to store each component.
26const SCALE: u128 = 100;
27
28/// Rounds up the given value to the nearest multiple of the mask.
29///
30/// # Panics
31/// Panics if the `mask` is zero.
32fn round_up<T>(value: T, mask: T) -> T
33where
34	T: One + Zero + Copy + Rem<Output = T> + Div<Output = T>,
35	<T as Rem>::Output: PartialEq,
36{
37	let rest = if value % mask == T::zero() { T::zero() } else { T::one() };
38	value / mask + rest
39}
40
41/// Rounds up the log2 of the given value to the nearest integer.
42fn log2_round_up<T>(val: T) -> u128
43where
44	T: Into<u128>,
45{
46	let val = val.into();
47	val.checked_ilog2()
48		.map(|v| if 1u128 << v == val { v } else { v + 1 })
49		.unwrap_or(0) as u128
50}
51
52mod private {
53	pub trait Sealed {}
54	impl Sealed for () {}
55}
56
57/// Encodes/Decodes EVM gas values.
58///
59/// # Note
60///
61/// This is defined as a trait rather than standalone functions to allow
62/// it to be added as an associated type to [`crate::Config`]. This way,
63/// it can be invoked without requiring the implementation bounds to be
64/// explicitly specified.
65///
66/// This trait is sealed and cannot be implemented by downstream crates.
67pub trait GasEncoder<Balance>: private::Sealed {
68	/// Encodes all components (deposit limit, weight reference time, and proof size) into a single
69	/// gas value.
70	fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256;
71
72	/// Decodes the weight and deposit from the encoded gas value.
73	/// Returns `None` if the gas value is invalid
74	fn decode(gas: U256) -> Option<(Weight, Balance)>;
75
76	/// Returns the encoded values of the specified weight and deposit.
77	fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) {
78		let encoded = Self::encode(U256::zero(), weight, deposit);
79		Self::decode(encoded).expect("encoded values should be decodable; qed")
80	}
81}
82
83impl<Balance> GasEncoder<Balance> for ()
84where
85	Balance: Zero + One + CheckedShl + Into<u128>,
86{
87	/// The encoding follows the pattern `g...grrppdd`, where:
88	/// - `dd`: log2 Deposit value, encoded in the lowest 2 digits.
89	/// - `pp`: log2 Proof size, encoded in the next 2 digits.
90	/// - `rr`: log2 Reference time, encoded in the next 2 digits.
91	/// - `g...g`: Gas limit, encoded in the highest digits.
92	///
93	/// # Note
94	/// - The deposit value is maxed by 2^99 for u128 balance, and 2^63 for u64 balance.
95	fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 {
96		let deposit: u128 = deposit.into();
97		let deposit_component = log2_round_up(deposit);
98
99		let proof_size = weight.proof_size();
100		let proof_size_component = SCALE * log2_round_up(proof_size);
101
102		let ref_time = weight.ref_time();
103		let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time);
104
105		let components = U256::from(deposit_component + proof_size_component + ref_time_component);
106
107		let raw_gas_mask = U256::from(SCALE).pow(3.into());
108		let raw_gas_component = if gas_limit <= components {
109			U256::zero()
110		} else {
111			round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask)
112		};
113
114		components.saturating_add(raw_gas_component)
115	}
116
117	fn decode(gas: U256) -> Option<(Weight, Balance)> {
118		let deposit = gas % SCALE;
119
120		// Casting with as_u32 is safe since all values are maxed by `SCALE`.
121		let deposit = deposit.as_u32();
122		let proof_time = ((gas / SCALE) % SCALE).as_u32();
123		let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32();
124
125		let ref_weight = match ref_time {
126			0 => 0,
127			64 => u64::MAX,
128			_ => 1u64.checked_shl(ref_time)?,
129		};
130
131		let proof_weight = match proof_time {
132			0 => 0,
133			64 => u64::MAX,
134			_ => 1u64.checked_shl(proof_time)?,
135		};
136
137		let weight = Weight::from_parts(ref_weight, proof_weight);
138
139		let deposit = match deposit {
140			0 => Balance::zero(),
141			_ => Balance::one().checked_shl(deposit)?,
142		};
143
144		Some((weight, deposit))
145	}
146}
147
148#[cfg(test)]
149mod test {
150	use super::*;
151
152	#[test]
153	fn test_gas_encoding_decoding_works() {
154		let raw_gas_limit = 111_111_999_999_999u128;
155		let weight = Weight::from_parts(222_999_999, 333_999_999);
156		let deposit = 444_999_999u64;
157
158		let encoded_gas = <() as GasEncoder<u64>>::encode(raw_gas_limit.into(), weight, deposit);
159		assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128));
160		assert!(encoded_gas > raw_gas_limit.into());
161
162		let (decoded_weight, decoded_deposit) =
163			<() as GasEncoder<u64>>::decode(encoded_gas).unwrap();
164		assert!(decoded_weight.all_gte(weight));
165		assert!(weight.mul(2).all_gte(weight));
166
167		assert!(decoded_deposit >= deposit);
168		assert!(deposit * 2 >= decoded_deposit);
169
170		assert_eq!(
171			(decoded_weight, decoded_deposit),
172			<() as GasEncoder<u64>>::as_encoded_values(weight, deposit)
173		);
174	}
175
176	#[test]
177	fn test_encoding_zero_values_work() {
178		let encoded_gas = <() as GasEncoder<u64>>::encode(
179			Default::default(),
180			Default::default(),
181			Default::default(),
182		);
183
184		assert_eq!(encoded_gas, U256::from(0));
185
186		let (decoded_weight, decoded_deposit) =
187			<() as GasEncoder<u64>>::decode(encoded_gas).unwrap();
188		assert_eq!(Weight::default(), decoded_weight);
189		assert_eq!(0u64, decoded_deposit);
190
191		let encoded_gas =
192			<() as GasEncoder<u64>>::encode(U256::from(1), Default::default(), Default::default());
193		assert_eq!(encoded_gas, U256::from(1000000));
194	}
195
196	#[test]
197	fn test_encoding_max_values_work() {
198		let max_weight = Weight::from_parts(u64::MAX, u64::MAX);
199		let max_deposit = 1u64 << 63;
200		let encoded_gas =
201			<() as GasEncoder<u64>>::encode(Default::default(), max_weight, max_deposit);
202
203		assert_eq!(encoded_gas, U256::from(646463));
204
205		let (decoded_weight, decoded_deposit) =
206			<() as GasEncoder<u64>>::decode(encoded_gas).unwrap();
207		assert_eq!(max_weight, decoded_weight);
208		assert_eq!(max_deposit, decoded_deposit);
209	}
210
211	#[test]
212	fn test_overflow() {
213		assert_eq!(None, <() as GasEncoder<u64>>::decode(65_00u128.into()), "Invalid proof size");
214		assert_eq!(None, <() as GasEncoder<u64>>::decode(65_00_00u128.into()), "Invalid ref_time");
215	}
216}