mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 07:01:05 +00:00
contracts: Allow indeterministic instructions off-chain (#12469)
* Allow indetermistic instructions off-chain * Apply suggestions from code review Co-authored-by: Sasha Gryaznov <hi@agryaznov.com> * fmt Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
committed by
GitHub
parent
d0dcf008ec
commit
3ae4be8662
@@ -201,7 +201,7 @@ pub fn reinstrument<T: Config>(
|
||||
// as the contract is already deployed and every change in size would be the result
|
||||
// of changes in the instrumentation algorithm controlled by the chain authors.
|
||||
prefab_module.code = WeakBoundedVec::force_from(
|
||||
prepare::reinstrument_contract::<T>(&original_code, schedule)
|
||||
prepare::reinstrument_contract::<T>(&original_code, schedule, prefab_module.determinism)
|
||||
.map_err(|_| <Error<T>>::CodeRejected)?,
|
||||
Some("Contract exceeds limit after re-instrumentation."),
|
||||
);
|
||||
|
||||
@@ -37,6 +37,7 @@ use crate::{
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::dispatch::{DispatchError, DispatchResult};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory};
|
||||
use sp_std::prelude::*;
|
||||
#[cfg(test)]
|
||||
@@ -66,6 +67,10 @@ pub struct PrefabWasmModule<T: Config> {
|
||||
maximum: u32,
|
||||
/// Code instrumented with the latest schedule.
|
||||
code: RelaxedCodeVec<T>,
|
||||
/// A code that might contain non deterministic features and is therefore never allowed
|
||||
/// to be run on chain. Specifically this code can never be instantiated into a contract
|
||||
/// and can just be used through a delegate call.
|
||||
determinism: Determinism,
|
||||
/// The uninstrumented, pristine version of the code.
|
||||
///
|
||||
/// It is not stored because the pristine code has its own storage item. The value
|
||||
@@ -102,6 +107,27 @@ pub struct OwnerInfo<T: Config> {
|
||||
refcount: u64,
|
||||
}
|
||||
|
||||
/// Defines the required determinism level of a wasm blob when either running or uploading code.
|
||||
#[derive(
|
||||
Clone, Copy, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen, RuntimeDebug, PartialEq, Eq,
|
||||
)]
|
||||
pub enum Determinism {
|
||||
/// The execution should be deterministic and hence no indeterministic instructions are
|
||||
/// allowed.
|
||||
///
|
||||
/// Dispatchables always use this mode in order to make on-chain execution deterministic.
|
||||
Deterministic,
|
||||
/// Allow calling or uploading an indeterministic code.
|
||||
///
|
||||
/// This is only possible when calling into `pallet-contracts` directly via
|
||||
/// [`crate::Pallet::bare_call`].
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// **Never** use this mode for on-chain execution.
|
||||
AllowIndeterminism,
|
||||
}
|
||||
|
||||
impl ExportedFunction {
|
||||
/// The wasm export name for the function.
|
||||
fn identifier(&self) -> &str {
|
||||
@@ -124,11 +150,13 @@ where
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<Self, (DispatchError, &'static str)> {
|
||||
let module = prepare::prepare_contract(
|
||||
original_code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
schedule,
|
||||
owner,
|
||||
determinism,
|
||||
)?;
|
||||
Ok(module)
|
||||
}
|
||||
@@ -258,6 +286,10 @@ where
|
||||
fn code_len(&self) -> u32 {
|
||||
self.code.len() as u32
|
||||
}
|
||||
|
||||
fn is_deterministic(&self) -> bool {
|
||||
matches!(self.determinism, Determinism::Deterministic)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -551,8 +583,13 @@ mod tests {
|
||||
fn execute<E: BorrowMut<MockExt>>(wat: &str, input_data: Vec<u8>, mut ext: E) -> ExecResult {
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let schedule = crate::Schedule::default();
|
||||
let executable =
|
||||
PrefabWasmModule::<<MockExt as Ext>::T>::from_code(wasm, &schedule, ALICE).unwrap();
|
||||
let executable = PrefabWasmModule::<<MockExt as Ext>::T>::from_code(
|
||||
wasm,
|
||||
&schedule,
|
||||
ALICE,
|
||||
Determinism::Deterministic,
|
||||
)
|
||||
.unwrap();
|
||||
executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data)
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
use crate::{
|
||||
chain_extension::ChainExtension,
|
||||
storage::meter::Diff,
|
||||
wasm::{env_def::ImportSatisfyCheck, OwnerInfo, PrefabWasmModule},
|
||||
wasm::{env_def::ImportSatisfyCheck, Determinism, OwnerInfo, PrefabWasmModule},
|
||||
AccountIdOf, CodeVec, Config, Error, Schedule,
|
||||
};
|
||||
use codec::{Encode, MaxEncodedLen};
|
||||
@@ -182,8 +182,8 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(self) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(&self.module);
|
||||
fn inject_gas_metering(self, determinism: Determinism) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(&self.module, determinism);
|
||||
let contract_module =
|
||||
wasm_instrument::gas_metering::inject(self.module, &gas_rules, "seal0")
|
||||
.map_err(|_| "gas instrumentation failed")?;
|
||||
@@ -369,6 +369,7 @@ fn get_memory_limits<T: Config>(
|
||||
fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<(Vec<u8>, (u32, u32)), &'static str> {
|
||||
let result = (|| {
|
||||
let contract_module = ContractModule::new(original_code, schedule)?;
|
||||
@@ -376,17 +377,20 @@ fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
|
||||
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
|
||||
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
|
||||
|
||||
if matches!(determinism, Determinism::Deterministic) {
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
}
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
let memory_limits =
|
||||
get_memory_limits(contract_module.scan_imports::<C>(&disallowed_imports)?, schedule)?;
|
||||
|
||||
let code = contract_module
|
||||
.inject_gas_metering()?
|
||||
.inject_gas_metering(determinism)?
|
||||
.inject_stack_height_metering()?
|
||||
.into_wasm_code()?;
|
||||
|
||||
@@ -404,9 +408,11 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
original_code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
|
||||
let (code, (initial, maximum)) = check_and_instrument::<C, T>(original_code.as_ref(), schedule)
|
||||
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
|
||||
let (code, (initial, maximum)) =
|
||||
check_and_instrument::<C, T>(original_code.as_ref(), schedule, determinism)
|
||||
.map_err(|msg| (<Error<T>>::CodeRejected.into(), msg))?;
|
||||
let original_code_len = original_code.len();
|
||||
|
||||
let mut module = PrefabWasmModule {
|
||||
@@ -414,6 +420,7 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
initial,
|
||||
maximum,
|
||||
code: code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||
determinism,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code),
|
||||
owner_info: None,
|
||||
@@ -449,8 +456,9 @@ pub fn prepare_contract<T: Config>(
|
||||
original_code: CodeVec<T>,
|
||||
schedule: &Schedule<T>,
|
||||
owner: AccountIdOf<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<PrefabWasmModule<T>, (DispatchError, &'static str)> {
|
||||
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner)
|
||||
do_preparation::<super::runtime::Env, T>(original_code, schedule, owner, determinism)
|
||||
}
|
||||
|
||||
/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`]
|
||||
@@ -461,8 +469,9 @@ pub fn prepare_contract<T: Config>(
|
||||
pub fn reinstrument_contract<T: Config>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
determinism: Determinism,
|
||||
) -> Result<Vec<u8>, &'static str> {
|
||||
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule)?.0)
|
||||
Ok(check_and_instrument::<super::runtime::Env, T>(original_code, schedule, determinism)?.0)
|
||||
}
|
||||
|
||||
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
|
||||
@@ -495,6 +504,7 @@ pub mod benchmarking {
|
||||
maximum: memory_limits.1,
|
||||
code_hash: T::Hashing::hash(&original_code),
|
||||
original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?),
|
||||
determinism: Determinism::Deterministic,
|
||||
code: contract_module
|
||||
.into_wasm_code()?
|
||||
.try_into()
|
||||
@@ -572,7 +582,7 @@ mod tests {
|
||||
},
|
||||
.. Default::default()
|
||||
};
|
||||
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE);
|
||||
let r = do_preparation::<env::Env, Test>(wasm, &schedule, ALICE, Determinism::Deterministic);
|
||||
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2384,6 +2384,7 @@ pub mod env {
|
||||
/// 2. Contracts using this API can't be assumed as having deterministic addresses. Said another
|
||||
/// way, when using this API you lose the guarantee that an address always identifies a specific
|
||||
/// code hash.
|
||||
///
|
||||
/// 3. If a contract calls into itself after changing its code the new call would use
|
||||
/// the new code. However, if the original caller panics after returning from the sub call it
|
||||
/// would revert the changes made by `seal_set_code_hash` and the next caller would use
|
||||
|
||||
Reference in New Issue
Block a user