mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 19:51:05 +00:00
contracts: Move Schedule from Storage to Config (#8773)
* Move `Schedule` from Storage to Config * Updated CHANGELOG * Fix nits from review * Fix migration * Print the debug buffer as tracing message * Use `debug` instead of `trace` and update README * Add additional assert to test * Rename `schedule_version` to `instruction_weights_version` * Fixed typo * Added more comments to wat fixtures * Add clarification for the `debug_message` field
This commit is contained in:
committed by
GitHub
parent
3c0270fe57
commit
1ac95b6ba6
@@ -132,11 +132,9 @@ where
|
||||
prefab_module.code_hash = code_hash;
|
||||
|
||||
if let Some((schedule, gas_meter)) = reinstrument {
|
||||
if prefab_module.schedule_version < schedule.version {
|
||||
// The current schedule version is greater than the version of the one cached
|
||||
// in the storage.
|
||||
//
|
||||
// We need to re-instrument the code with the latest schedule here.
|
||||
if prefab_module.instruction_weights_version < schedule.instruction_weights.version {
|
||||
// The instruction weights have changed.
|
||||
// We need to re-instrument the code with the new instruction weights.
|
||||
gas_meter.charge(InstrumentToken(prefab_module.original_code_len))?;
|
||||
private::reinstrument(&mut prefab_module, schedule)?;
|
||||
}
|
||||
@@ -158,7 +156,7 @@ mod private {
|
||||
let original_code = <PristineCode<T>>::get(&prefab_module.code_hash)
|
||||
.ok_or_else(|| Error::<T>::CodeNotFound)?;
|
||||
prefab_module.code = prepare::reinstrument_contract::<T>(original_code, schedule)?;
|
||||
prefab_module.schedule_version = schedule.version;
|
||||
prefab_module.instruction_weights_version = schedule.instruction_weights.version;
|
||||
<CodeStorage<T>>::insert(&prefab_module.code_hash, &*prefab_module);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -45,16 +45,16 @@ pub use tests::MockExt;
|
||||
/// # Note
|
||||
///
|
||||
/// This data structure is mostly immutable once created and stored. The exceptions that
|
||||
/// can be changed by calling a contract are `refcount`, `schedule_version` and `code`.
|
||||
/// can be changed by calling a contract are `refcount`, `instruction_weights_version` and `code`.
|
||||
/// `refcount` can change when a contract instantiates a new contract or self terminates.
|
||||
/// `schedule_version` and `code` when a contract with an outdated instrumention is called.
|
||||
/// Therefore one must be careful when holding any in-memory representation of this type while
|
||||
/// calling into a contract as those fields can get out of date.
|
||||
/// `instruction_weights_version` and `code` when a contract with an outdated instrumention is
|
||||
/// called. Therefore one must be careful when holding any in-memory representation of this
|
||||
/// type while calling into a contract as those fields can get out of date.
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
pub struct PrefabWasmModule<T: Config> {
|
||||
/// Version of the schedule with which the code was instrumented.
|
||||
/// Version of the instruction weights with which the code was instrumented.
|
||||
#[codec(compact)]
|
||||
schedule_version: u32,
|
||||
instruction_weights_version: u32,
|
||||
/// Initial memory size of a contract's sandbox.
|
||||
#[codec(compact)]
|
||||
initial: u32,
|
||||
@@ -140,6 +140,12 @@ where
|
||||
pub fn refcount(&self) -> u64 {
|
||||
self.refcount
|
||||
}
|
||||
|
||||
/// Decrement instruction_weights_version by 1. Panics if it is already 0.
|
||||
#[cfg(test)]
|
||||
pub fn decrement_version(&mut self) {
|
||||
self.instruction_weights_version = self.instruction_weights_version.checked_sub(1).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Executable<T> for PrefabWasmModule<T>
|
||||
@@ -297,6 +303,7 @@ mod tests {
|
||||
schedule: Schedule<Test>,
|
||||
rent_params: RentParams<Test>,
|
||||
gas_meter: GasMeter<Test>,
|
||||
debug_buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for MockExt {
|
||||
@@ -312,6 +319,7 @@ mod tests {
|
||||
schedule: Default::default(),
|
||||
rent_params: Default::default(),
|
||||
gas_meter: GasMeter::new(10_000_000_000),
|
||||
debug_buffer: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,6 +455,10 @@ mod tests {
|
||||
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
|
||||
&mut self.gas_meter
|
||||
}
|
||||
fn append_debug_buffer(&mut self, msg: &str) -> bool {
|
||||
self.debug_buffer.extend(msg.as_bytes());
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<E: BorrowMut<MockExt>>(
|
||||
@@ -1845,4 +1857,71 @@ mod tests {
|
||||
let rent_params = Bytes(<RentParams<Test>>::default().encode());
|
||||
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
|
||||
}
|
||||
|
||||
const CODE_DEBUG_MESSAGE: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "Hello World!")
|
||||
|
||||
(func (export "call")
|
||||
(call $seal_debug_message
|
||||
(i32.const 0) ;; Pointer to the text buffer
|
||||
(i32.const 12) ;; The size of the buffer
|
||||
)
|
||||
drop
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn debug_message_works() {
|
||||
let mut ext = MockExt::default();
|
||||
execute(
|
||||
CODE_DEBUG_MESSAGE,
|
||||
vec![],
|
||||
&mut ext,
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(std::str::from_utf8(&ext.debug_buffer).unwrap(), "Hello World!");
|
||||
}
|
||||
|
||||
const CODE_DEBUG_MESSAGE_FAIL: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "\fc")
|
||||
|
||||
(func (export "call")
|
||||
(call $seal_debug_message
|
||||
(i32.const 0) ;; Pointer to the text buffer
|
||||
(i32.const 1) ;; The size of the buffer
|
||||
)
|
||||
drop
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn debug_message_invalid_utf8_fails() {
|
||||
let mut ext = MockExt::default();
|
||||
let result = execute(
|
||||
CODE_DEBUG_MESSAGE_FAIL,
|
||||
vec![],
|
||||
&mut ext,
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(ExecError {
|
||||
error: Error::<Test>::DebugMessageInvalidUTF8.into(),
|
||||
origin: ErrorOrigin::Caller,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,12 +343,6 @@ impl<'a, T: Config> ContractModule<'a, T> {
|
||||
.get(*type_idx as usize)
|
||||
.ok_or_else(|| "validation: import entry points to a non-existent type")?;
|
||||
|
||||
// We disallow importing `seal_println` unless debug features are enabled,
|
||||
// which should only be allowed on a dev chain
|
||||
if !self.schedule.enable_println && import.field().as_bytes() == b"seal_println" {
|
||||
return Err("module imports `seal_println` but debug features disabled");
|
||||
}
|
||||
|
||||
if !T::ChainExtension::enabled() &&
|
||||
import.field().as_bytes() == b"seal_call_chain_extension"
|
||||
{
|
||||
@@ -439,7 +433,7 @@ fn do_preparation<C: ImportSatisfyCheck, T: Config>(
|
||||
schedule,
|
||||
)?;
|
||||
Ok(PrefabWasmModule {
|
||||
schedule_version: schedule.version,
|
||||
instruction_weights_version: schedule.instruction_weights.version,
|
||||
initial,
|
||||
maximum,
|
||||
_reserved: None,
|
||||
@@ -505,7 +499,7 @@ pub mod benchmarking {
|
||||
let contract_module = ContractModule::new(&original_code, schedule)?;
|
||||
let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?;
|
||||
Ok(PrefabWasmModule {
|
||||
schedule_version: schedule.version,
|
||||
instruction_weights_version: schedule.instruction_weights.version,
|
||||
initial: memory_limits.0,
|
||||
maximum: memory_limits.1,
|
||||
_reserved: None,
|
||||
@@ -547,8 +541,6 @@ mod tests {
|
||||
|
||||
// new version of nop with other data type for argumebt
|
||||
[seal1] nop(_ctx, _unused: i32) => { unreachable!(); },
|
||||
|
||||
[seal0] seal_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -938,36 +930,6 @@ mod tests {
|
||||
"#,
|
||||
Err("module imports a non-existent function")
|
||||
);
|
||||
|
||||
prepare_test!(seal_println_debug_disabled,
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "seal_println" (func $seal_println (param i32 i32)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module imports `seal_println` but debug features disabled")
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn seal_println_debug_enabled() {
|
||||
let wasm = wat::parse_str(
|
||||
r#"
|
||||
(module
|
||||
(import "seal0" "seal_println" (func $seal_println (param i32 i32)))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#
|
||||
).unwrap();
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.enable_println = true;
|
||||
let r = do_preparation::<env::Test, crate::tests::Test>(wasm, &schedule);
|
||||
assert_matches::assert_matches!(r, Ok(_));
|
||||
}
|
||||
}
|
||||
|
||||
mod entrypoints {
|
||||
|
||||
@@ -71,6 +71,9 @@ pub enum ReturnCode {
|
||||
/// The contract that was called is either no contract at all (a plain account)
|
||||
/// or is a tombstone.
|
||||
NotCallable = 8,
|
||||
/// The call to `seal_debug_message` had no effect because debug message
|
||||
/// recording was disabled.
|
||||
LoggingDisabled = 9,
|
||||
}
|
||||
|
||||
impl ConvertibleToWasm for ReturnCode {
|
||||
@@ -175,6 +178,8 @@ pub enum RuntimeCosts {
|
||||
Random,
|
||||
/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
|
||||
DepositEvent{num_topic: u32, len: u32},
|
||||
/// Weight of calling `seal_debug_message`.
|
||||
DebugMessage,
|
||||
/// Weight of calling `seal_set_rent_allowance`.
|
||||
SetRentAllowance,
|
||||
/// Weight of calling `seal_set_storage` for the given storage item size.
|
||||
@@ -255,6 +260,7 @@ impl RuntimeCosts {
|
||||
DepositEvent{num_topic, len} => s.deposit_event
|
||||
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
|
||||
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
|
||||
DebugMessage => s.debug_message,
|
||||
SetRentAllowance => s.set_rent_allowance,
|
||||
SetStorage(len) => s.set_storage
|
||||
.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())),
|
||||
@@ -802,7 +808,7 @@ define_env!(Env, <E: Ext>,
|
||||
ctx.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
|
||||
}
|
||||
let charged = ctx.charge_gas(
|
||||
RuntimeCosts::CallSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
|
||||
RuntimeCosts::CallSurchargeCodeSize(<E::T as Config>::Schedule::get().limits.code_len)
|
||||
)?;
|
||||
let ext = &mut ctx.ext;
|
||||
let call_outcome = ext.call(gas, callee, value, input_data);
|
||||
@@ -887,7 +893,9 @@ define_env!(Env, <E: Ext>,
|
||||
let input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
|
||||
let salt = ctx.read_sandbox_memory(salt_ptr, salt_len)?;
|
||||
let charged = ctx.charge_gas(
|
||||
RuntimeCosts::InstantiateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
|
||||
RuntimeCosts::InstantiateSurchargeCodeSize(
|
||||
<E::T as Config>::Schedule::get().limits.code_len
|
||||
)
|
||||
)?;
|
||||
let ext = &mut ctx.ext;
|
||||
let instantiate_outcome = ext.instantiate(gas, code_hash, value, input_data, &salt);
|
||||
@@ -937,7 +945,9 @@ define_env!(Env, <E: Ext>,
|
||||
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
|
||||
|
||||
let charged = ctx.charge_gas(
|
||||
RuntimeCosts::TerminateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
|
||||
RuntimeCosts::TerminateSurchargeCodeSize(
|
||||
<E::T as Config>::Schedule::get().limits.code_len
|
||||
)
|
||||
)?;
|
||||
let (result, code_len) = match ctx.ext.terminate(&beneficiary) {
|
||||
Ok(len) => (Ok(()), len),
|
||||
@@ -1266,7 +1276,7 @@ define_env!(Env, <E: Ext>,
|
||||
delta
|
||||
};
|
||||
|
||||
let max_len = <E::T as Config>::MaxCodeSize::get();
|
||||
let max_len = <E::T as Config>::Schedule::get().limits.code_len;
|
||||
let charged = ctx.charge_gas(RuntimeCosts::RestoreToSurchargeCodeSize {
|
||||
caller_code: max_len,
|
||||
tombstone_code: max_len,
|
||||
@@ -1380,15 +1390,33 @@ define_env!(Env, <E: Ext>,
|
||||
)?)
|
||||
},
|
||||
|
||||
// Prints utf8 encoded string from the data buffer.
|
||||
// Only available on `--dev` chains.
|
||||
// This function may be removed at any time, superseded by a more general contract debugging feature.
|
||||
[seal0] seal_println(ctx, str_ptr: u32, str_len: u32) => {
|
||||
let data = ctx.read_sandbox_memory(str_ptr, str_len)?;
|
||||
if let Ok(utf8) = core::str::from_utf8(&data) {
|
||||
log::info!(target: "runtime::contracts", "seal_println: {}", utf8);
|
||||
// Emit a custom debug message.
|
||||
//
|
||||
// No newlines are added to the supplied message.
|
||||
// Specifying invalid UTF-8 triggers a trap.
|
||||
//
|
||||
// This is a no-op if debug message recording is disabled which is always the case
|
||||
// when the code is executing on-chain. The message is interpreted as UTF-8 and
|
||||
// appended to the debug buffer which is then supplied to the calling RPC client.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// Even though no action is taken when debug message recording is disabled there is still
|
||||
// a non trivial overhead (and weight cost) associated with calling this function. Contract
|
||||
// languages should remove calls to this function (either at runtime or compile time) when
|
||||
// not being executed as an RPC. For example, they could allow users to disable logging
|
||||
// through compile time flags (cargo features) for on-chain deployment. Additionally, the
|
||||
// return value of this function can be cached in order to prevent further calls at runtime.
|
||||
[seal0] seal_debug_message(ctx, str_ptr: u32, str_len: u32) -> ReturnCode => {
|
||||
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
|
||||
if ctx.ext.append_debug_buffer("") {
|
||||
let data = ctx.read_sandbox_memory(str_ptr, str_len)?;
|
||||
let msg = core::str::from_utf8(&data)
|
||||
.map_err(|_| <Error<E::T>>::DebugMessageInvalidUTF8)?;
|
||||
ctx.ext.append_debug_buffer(msg);
|
||||
return Ok(ReturnCode::Success);
|
||||
}
|
||||
Ok(())
|
||||
Ok(ReturnCode::LoggingDisabled)
|
||||
},
|
||||
|
||||
// Stores the current block number of the current contract into the supplied buffer.
|
||||
|
||||
Reference in New Issue
Block a user