Skip to content

Commit

Permalink
Contracts upload with Determinism::Enforced when possible
Browse files Browse the repository at this point in the history
Update benchmarking
  • Loading branch information
pgherveou committed Mar 1, 2024
1 parent 81d447a commit f3ace02
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 17 deletions.
4 changes: 2 additions & 2 deletions substrate/frame/contracts/src/benchmarking/code.rs
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ 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
Expand All @@ -274,7 +274,7 @@ impl<T: Config> WasmModule<T> {
let mut module =
ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
let body = Some(body::repeated_with_locals(
&[Local::new(1, ValueType::I32)],
&[Local::new(1, if use_float { ValueType::F32 } else { ValueType::I32 })],
expansions,
&EXPANSION,
));
Expand Down
23 changes: 18 additions & 5 deletions substrate/frame/contracts/src/benchmarking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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)
Expand Down Expand Up @@ -468,19 +468,32 @@ 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));
}

#[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));
}

// 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`).
Expand Down
7 changes: 6 additions & 1 deletion substrate/frame/contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,12 @@ pub mod pallet {
/// only be instantiated by permissioned entities. The same is true when uploading
/// through [`Self::instantiate_with_code`].
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::upload_code(code.len() as u32))]
#[pallet::weight(
match determinism {
Determinism::Enforced => T::WeightInfo::upload_code_determinism_enforced(code.len() as u32),
Determinism::Relaxed => T::WeightInfo::upload_code_determinism_relaxed(code.len() as u32),
}
)]
pub fn upload_code(
origin: OriginFor<T>,
code: Vec<u8>,
Expand Down
20 changes: 20 additions & 0 deletions substrate/frame/contracts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5155,6 +5155,26 @@ fn deposit_limit_honors_min_leftover() {
});
}

#[test]
fn upload_should_enforce_deterministic_mode_when_possible() {
let upload = |fixture, determinism| {
let (wasm, code_hash) = compile_module::<Test>(fixture).unwrap();
ExtBuilder::default()
.build()
.execute_with(|| -> Result<Determinism, DispatchError> {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);
Contracts::bare_upload_code(ALICE, wasm, None, determinism)?;
let info = CodeInfoOf::<Test>::get(code_hash).unwrap();
Ok(info.determinism())
})
};

assert_eq!(upload("dummy", Determinism::Enforced), Ok(Determinism::Enforced));
assert_eq!(upload("dummy", Determinism::Relaxed), Ok(Determinism::Enforced));
assert_eq!(upload("float_instruction", Determinism::Relaxed), Ok(Determinism::Relaxed));
assert!(upload("float_instruction", Determinism::Enforced).is_err());
}

#[test]
fn cannot_instantiate_indeterministic_code() {
let (wasm, code_hash) = compile_module::<Test>("float_instruction").unwrap();
Expand Down
4 changes: 4 additions & 0 deletions substrate/frame/contracts/src/wasm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,10 @@ impl<T: Config> CodeInfo<T> {
}
}

pub fn determinism(&self) -> Determinism {
self.determinism
}

/// Returns reference count of the module.
pub fn refcount(&self) -> u64 {
self.refcount
Expand Down
25 changes: 19 additions & 6 deletions substrate/frame/contracts/src/wasm/prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ impl LoadedModule {
}

let engine = Engine::new(&config);
let module = Module::new(&engine, code).map_err(|_| "Can't load the module into wasmi!")?;
let module = Module::new(&engine, code).map_err(|err| {
log::debug!(target: LOG_TARGET, "Module creation failed: {:?}", err);
"Can't load the module into wasmi!"
})?;

// Return a `LoadedModule` instance with
// __valid__ module.
Expand Down Expand Up @@ -220,7 +223,7 @@ impl LoadedModule {
fn validate<E, T>(
code: &[u8],
schedule: &Schedule<T>,
determinism: Determinism,
determinism: &mut Determinism,
) -> Result<(), (DispatchError, &'static str)>
where
E: Environment<()>,
Expand All @@ -229,7 +232,17 @@ where
(|| {
// We check that the module is generally valid,
// and does not have restricted WebAssembly features, here.
let contract_module = LoadedModule::new::<T>(code, determinism, None)?;
let contract_module = match *determinism {
Determinism::Relaxed =>
if let Ok(module) = LoadedModule::new::<T>(code, Determinism::Enforced, None) {
*determinism = Determinism::Enforced;
module
} else {
LoadedModule::new::<T>(code, Determinism::Relaxed, None)?
},
Determinism::Enforced => LoadedModule::new::<T>(code, Determinism::Enforced, None)?,
};

// The we check that module satisfies constraints the pallet puts on contracts.
contract_module.scan_exports()?;
contract_module.scan_imports::<T>(schedule)?;
Expand All @@ -252,7 +265,7 @@ where
&code,
(),
schedule,
determinism,
*determinism,
stack_limits,
AllowDeprecatedInterface::No,
)
Expand All @@ -276,13 +289,13 @@ pub fn prepare<E, T>(
code: CodeVec<T>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
mut determinism: Determinism,
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
{
validate::<E, T>(code.as_ref(), schedule, determinism)?;
validate::<E, T>(code.as_ref(), schedule, &mut determinism)?;

// Calculate deposit for storing contract code and `code_info` in two different storage items.
let code_len = code.len() as u32;
Expand Down
40 changes: 37 additions & 3 deletions substrate/frame/contracts/src/weights.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f3ace02

Please sign in to comment.