pallet-revive 0.7.2

FRAME pallet for PolkaVM contracts.
Documentation
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// 	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Encodes/Decodes EVM gas values.

use crate::Weight;
use core::ops::{Div, Rem};
use frame_support::pallet_prelude::CheckedShl;
use sp_arithmetic::traits::{One, Zero};
use sp_core::U256;

// We use 3 digits to store each component.
const SCALE: u128 = 100;

/// Rounds up the given value to the nearest multiple of the mask.
///
/// # Panics
/// Panics if the `mask` is zero.
fn round_up<T>(value: T, mask: T) -> T
where
	T: One + Zero + Copy + Rem<Output = T> + Div<Output = T>,
	<T as Rem>::Output: PartialEq,
{
	let rest = if value % mask == T::zero() { T::zero() } else { T::one() };
	value / mask + rest
}

/// Rounds up the log2 of the given value to the nearest integer.
fn log2_round_up<T>(val: T) -> u128
where
	T: Into<u128>,
{
	let val = val.into();
	val.checked_ilog2()
		.map(|v| if 1u128 << v == val { v } else { v + 1 })
		.unwrap_or(0) as u128
}

mod private {
	pub trait Sealed {}
	impl Sealed for () {}
}

/// Encodes/Decodes EVM gas values.
///
/// # Note
///
/// This is defined as a trait rather than standalone functions to allow
/// it to be added as an associated type to [`crate::Config`]. This way,
/// it can be invoked without requiring the implementation bounds to be
/// explicitly specified.
///
/// This trait is sealed and cannot be implemented by downstream crates.
pub trait GasEncoder<Balance>: private::Sealed {
	/// Encodes all components (deposit limit, weight reference time, and proof size) into a single
	/// gas value.
	fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256;

	/// Decodes the weight and deposit from the encoded gas value.
	/// Returns `None` if the gas value is invalid
	fn decode(gas: U256) -> Option<(Weight, Balance)>;

	/// Returns the encoded values of the specified weight and deposit.
	fn as_encoded_values(weight: Weight, deposit: Balance) -> (Weight, Balance) {
		let encoded = Self::encode(U256::zero(), weight, deposit);
		Self::decode(encoded).expect("encoded values should be decodable; qed")
	}
}

impl<Balance> GasEncoder<Balance> for ()
where
	Balance: Zero + One + CheckedShl + Into<u128>,
{
	/// The encoding follows the pattern `g...grrppdd`, where:
	/// - `dd`: log2 Deposit value, encoded in the lowest 2 digits.
	/// - `pp`: log2 Proof size, encoded in the next 2 digits.
	/// - `rr`: log2 Reference time, encoded in the next 2 digits.
	/// - `g...g`: Gas limit, encoded in the highest digits.
	///
	/// # Note
	/// - The deposit value is maxed by 2^99 for u128 balance, and 2^63 for u64 balance.
	fn encode(gas_limit: U256, weight: Weight, deposit: Balance) -> U256 {
		let deposit: u128 = deposit.into();
		let deposit_component = log2_round_up(deposit);

		let proof_size = weight.proof_size();
		let proof_size_component = SCALE * log2_round_up(proof_size);

		let ref_time = weight.ref_time();
		let ref_time_component = SCALE.pow(2) * log2_round_up(ref_time);

		let components = U256::from(deposit_component + proof_size_component + ref_time_component);

		let raw_gas_mask = U256::from(SCALE).pow(3.into());
		let raw_gas_component = if gas_limit <= components {
			U256::zero()
		} else {
			round_up(gas_limit, raw_gas_mask).saturating_mul(raw_gas_mask)
		};

		components.saturating_add(raw_gas_component)
	}

	fn decode(gas: U256) -> Option<(Weight, Balance)> {
		let deposit = gas % SCALE;

		// Casting with as_u32 is safe since all values are maxed by `SCALE`.
		let deposit = deposit.as_u32();
		let proof_time = ((gas / SCALE) % SCALE).as_u32();
		let ref_time = ((gas / SCALE.pow(2)) % SCALE).as_u32();

		let ref_weight = match ref_time {
			0 => 0,
			64 => u64::MAX,
			_ => 1u64.checked_shl(ref_time)?,
		};

		let proof_weight = match proof_time {
			0 => 0,
			64 => u64::MAX,
			_ => 1u64.checked_shl(proof_time)?,
		};

		let weight = Weight::from_parts(ref_weight, proof_weight);

		let deposit = match deposit {
			0 => Balance::zero(),
			_ => Balance::one().checked_shl(deposit)?,
		};

		Some((weight, deposit))
	}
}

#[cfg(test)]
mod test {
	use super::*;

	#[test]
	fn test_gas_encoding_decoding_works() {
		let raw_gas_limit = 111_111_999_999_999u128;
		let weight = Weight::from_parts(222_999_999, 333_999_999);
		let deposit = 444_999_999u64;

		let encoded_gas = <() as GasEncoder<u64>>::encode(raw_gas_limit.into(), weight, deposit);
		assert_eq!(encoded_gas, U256::from(111_112_000_282_929u128));
		assert!(encoded_gas > raw_gas_limit.into());

		let (decoded_weight, decoded_deposit) =
			<() as GasEncoder<u64>>::decode(encoded_gas).unwrap();
		assert!(decoded_weight.all_gte(weight));
		assert!(weight.mul(2).all_gte(weight));

		assert!(decoded_deposit >= deposit);
		assert!(deposit * 2 >= decoded_deposit);

		assert_eq!(
			(decoded_weight, decoded_deposit),
			<() as GasEncoder<u64>>::as_encoded_values(weight, deposit)
		);
	}

	#[test]
	fn test_encoding_zero_values_work() {
		let encoded_gas = <() as GasEncoder<u64>>::encode(
			Default::default(),
			Default::default(),
			Default::default(),
		);

		assert_eq!(encoded_gas, U256::from(0));

		let (decoded_weight, decoded_deposit) =
			<() as GasEncoder<u64>>::decode(encoded_gas).unwrap();
		assert_eq!(Weight::default(), decoded_weight);
		assert_eq!(0u64, decoded_deposit);

		let encoded_gas =
			<() as GasEncoder<u64>>::encode(U256::from(1), Default::default(), Default::default());
		assert_eq!(encoded_gas, U256::from(1000000));
	}

	#[test]
	fn test_encoding_max_values_work() {
		let max_weight = Weight::from_parts(u64::MAX, u64::MAX);
		let max_deposit = 1u64 << 63;
		let encoded_gas =
			<() as GasEncoder<u64>>::encode(Default::default(), max_weight, max_deposit);

		assert_eq!(encoded_gas, U256::from(646463));

		let (decoded_weight, decoded_deposit) =
			<() as GasEncoder<u64>>::decode(encoded_gas).unwrap();
		assert_eq!(max_weight, decoded_weight);
		assert_eq!(max_deposit, decoded_deposit);
	}

	#[test]
	fn test_overflow() {
		assert_eq!(None, <() as GasEncoder<u64>>::decode(65_00u128.into()), "Invalid proof size");
		assert_eq!(None, <() as GasEncoder<u64>>::decode(65_00_00u128.into()), "Invalid ref_time");
	}
}