Contracts upload with Determinism::Enforced when possible (#3540)

Co-authored-by: Cyrill Leutwiler <cyrill@parity.io>
Co-authored-by: command-bot <>
Co-authored-by: Alexander Theißen <alex.theissen@me.com>
This commit is contained in:
PG Herveou
2024-03-06 17:35:55 +01:00
committed by GitHub
parent f2f4b154d7
commit 9952786e1b
8 changed files with 756 additions and 636 deletions
@@ -26,7 +26,7 @@
use crate::Config;
use frame_support::traits::Get;
use sp_runtime::traits::Hash;
use sp_runtime::{traits::Hash, Saturating};
use sp_std::{borrow::ToOwned, prelude::*};
use wasm_instrument::parity_wasm::{
builder,
@@ -262,22 +262,25 @@ impl<T: Config> WasmModule<T> {
/// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
/// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
/// `code_location`: Whether to place the code into `deploy` or `call`.
pub fn sized(target_bytes: u32, code_location: Location) -> Self {
pub fn sized(target_bytes: u32, code_location: Location, use_float: bool) -> Self {
use self::elements::Instruction::{End, GetLocal, If, Return};
// Base size of a contract is 63 bytes and each expansion adds 6 bytes.
// We do one expansion less to account for the code section and function body
// size fields inside the binary wasm module representation which are leb128 encoded
// and therefore grow in size when the contract grows. We are not allowed to overshoot
// because of the maximum code size that is enforced by `instantiate_with_code`.
let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
let mut expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
const EXPANSION: [Instruction; 4] = [GetLocal(0), If(BlockType::NoResult), Return, End];
let mut locals = vec![Local::new(1, ValueType::I32)];
if use_float {
locals.push(Local::new(1, ValueType::F32));
locals.push(Local::new(2, ValueType::F32));
locals.push(Local::new(3, ValueType::F32));
expansions.saturating_dec();
}
let mut module =
ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
let body = Some(body::repeated_with_locals(
&[Local::new(1, ValueType::I32)],
expansions,
&EXPANSION,
));
let body = Some(body::repeated_with_locals(&locals, expansions, &EXPANSION));
match code_location {
Location::Call => module.call_body = body,
Location::Deploy => module.deploy_body = body,
@@ -362,7 +362,7 @@ benchmarks! {
call_with_code_per_byte {
let c in 0 .. T::MaxCodeLen::get();
let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::sized(c, Location::Deploy), vec![],
whitelisted_caller(), WasmModule::sized(c, Location::Deploy, false), vec![],
)?;
let value = Pallet::<T>::min_balance();
let origin = RawOrigin::Signed(instance.caller.clone());
@@ -389,7 +389,7 @@ benchmarks! {
let value = Pallet::<T>::min_balance();
let caller = whitelisted_caller();
T::Currency::set_balance(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call, false);
let origin = RawOrigin::Signed(caller.clone());
let addr = Contracts::<T>::contract_address(&caller, &hash, &input, &salt);
}: _(origin, value, Weight::MAX, None, code, input, salt)
@@ -468,19 +468,36 @@ benchmarks! {
// It creates a maximum number of metering blocks per byte.
// `c`: Size of the code in bytes.
#[pov_mode = Measured]
upload_code {
upload_code_determinism_enforced {
let c in 0 .. T::MaxCodeLen::get();
let caller = whitelisted_caller();
T::Currency::set_balance(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call);
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call, false);
let origin = RawOrigin::Signed(caller.clone());
}: _(origin, code, None, Determinism::Enforced)
}: upload_code(origin, code, None, Determinism::Enforced)
verify {
// uploading the code reserves some balance in the callers account
assert!(T::Currency::total_balance_on_hold(&caller) > 0u32.into());
assert!(<Contract<T>>::code_exists(&hash));
}
// Uploading code with [`Determinism::Relaxed`] should be more expensive than uploading code with [`Determinism::Enforced`],
// as we always try to save the code with [`Determinism::Enforced`] first.
#[pov_mode = Measured]
upload_code_determinism_relaxed {
let c in 0 .. T::MaxCodeLen::get();
let caller = whitelisted_caller();
T::Currency::set_balance(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c, Location::Call, true);
let origin = RawOrigin::Signed(caller.clone());
}: upload_code(origin, code, None, Determinism::Relaxed)
verify {
assert!(T::Currency::total_balance_on_hold(&caller) > 0u32.into());
assert!(<Contract<T>>::code_exists(&hash));
// Ensure that the benchmark follows the most expensive path, i.e., the code is saved with [`Determinism::Relaxed`] after trying to save it with [`Determinism::Enforced`].
assert_eq!(CodeInfoOf::<T>::get(&hash).unwrap().determinism(), Determinism::Relaxed);
}
// Removing code does not depend on the size of the contract because all the information
// needed to verify the removal claim (refcount, owner) is stored in a separate storage
// item (`CodeInfoOf`).