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:
Alexander Theißen
2021-05-13 21:56:11 +02:00
committed by GitHub
parent 3c0270fe57
commit 1ac95b6ba6
23 changed files with 1465 additions and 1056 deletions
@@ -24,7 +24,7 @@
//! we define this simple definition of a contract that can be passed to `create_code` that
//! compiles it down into a `WasmModule` that can be used as a contract's code.
use crate::{Config, CurrentSchedule};
use crate::Config;
use parity_wasm::elements::{
Instruction, Instructions, FuncBody, ValueType, BlockType, Section, CustomSection,
};
@@ -33,6 +33,7 @@ use sp_core::crypto::UncheckedFrom;
use sp_runtime::traits::Hash;
use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};
use sp_std::{prelude::*, convert::TryFrom, borrow::ToOwned};
use frame_support::traits::Get;
/// Pass to `create_code` in order to create a compiled `WasmModule`.
///
@@ -223,7 +224,7 @@ where
if def.inject_stack_metering {
code = inject_limiter(
code,
<CurrentSchedule<T>>::get().limits.stack_height
T::Schedule::get().limits.stack_height
)
.unwrap();
}
@@ -503,5 +504,5 @@ where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
<CurrentSchedule<T>>::get().limits.memory_pages
T::Schedule::get().limits.memory_pages
}
@@ -290,7 +290,7 @@ benchmarks! {
on_initialize_per_trie_key {
let k in 0..1024;
let instance = ContractWithStorage::<T>::new(k, T::MaxValueSize::get())?;
let instance = ContractWithStorage::<T>::new(k, T::Schedule::get().limits.payload_len)?;
Storage::<T>::queue_trie_for_deletion(&instance.contract.alive_info()?)?;
}: {
Storage::<T>::process_deletion_queue_batch(Weight::max_value())
@@ -311,23 +311,15 @@ benchmarks! {
// first time after a new schedule was deployed: For every new schedule a contract needs
// to re-run the instrumentation once.
instrument {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c * 1024);
Contracts::<T>::store_code_raw(code)?;
let mut module = PrefabWasmModule::from_storage_noinstr(hash)?;
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
}: {
Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
}
// This extrinsic is pretty much constant as it is only a simple setter.
update_schedule {
let schedule = Schedule {
version: 1,
.. Default::default()
};
}: _(RawOrigin::Root, schedule)
// This constructs a contract that is maximal expensive to instrument.
// It creates a maximum number of metering blocks per byte.
// The size of the salt influences the runtime because is is hashed in order to
@@ -340,7 +332,7 @@ benchmarks! {
// We cannot let `c` grow to the maximum code size because the code is not allowed
// to be larger than the maximum size **after instrumentation**.
instantiate_with_code {
let c in 0 .. Perbill::from_percent(50).mul_ceil(T::MaxCodeSize::get() / 1024);
let c in 0 .. Perbill::from_percent(50).mul_ceil(T::Schedule::get().limits.code_len / 1024);
let s in 0 .. code::max_pages::<T>() * 64;
let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into();
@@ -363,7 +355,7 @@ benchmarks! {
// `c`: Size of the code in kilobytes.
// `s`: Size of the salt in kilobytes.
instantiate {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let s in 0 .. code::max_pages::<T>() * 64;
let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into();
@@ -390,7 +382,7 @@ benchmarks! {
// part of `seal_input`.
// `c`: Size of the code in kilobytes.
call {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let data = vec![42u8; 1024];
let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), vec![], Endow::CollectRent
@@ -423,7 +415,7 @@ benchmarks! {
// the reward for removing them.
// `c`: Size of the code of the contract that should be evicted.
claim_surcharge {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), vec![], Endow::CollectRent
)?;
@@ -730,7 +722,7 @@ benchmarks! {
}
seal_terminate_per_code_kb {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
let beneficiary_bytes = beneficiary.encode();
let beneficiary_len = beneficiary_bytes.len();
@@ -771,7 +763,7 @@ benchmarks! {
// Restore just moves the trie id from origin to destination and therefore
// does not depend on the size of the destination contract. However, to not
// trigger any edge case we won't use an empty contract as destination.
let mut tombstone = ContractWithStorage::<T>::new(10, T::MaxValueSize::get())?;
let mut tombstone = ContractWithStorage::<T>::new(10, T::Schedule::get().limits.payload_len)?;
tombstone.evict()?;
let dest = tombstone.contract.account_id.encode();
@@ -847,14 +839,14 @@ benchmarks! {
// `t`: Code size of tombstone contract
// `d`: Number of supplied delta keys
seal_restore_to_per_code_kb_delta {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let t in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let t in 0 .. T::Schedule::get().limits.code_len / 1024;
let d in 0 .. API_BENCHMARK_BATCHES;
let mut tombstone = ContractWithStorage::<T>::with_code(
WasmModule::<T>::dummy_with_bytes(t * 1024), 0, 0
)?;
tombstone.evict()?;
let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?;
let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::Schedule::get().limits.payload_len)?;
let dest = tombstone.contract.account_id.encode();
let dest_len = dest.len();
@@ -938,7 +930,7 @@ benchmarks! {
seal_random {
let r in 0 .. API_BENCHMARK_BATCHES;
let pages = code::max_pages::<T>();
let subject_len = <CurrentSchedule<T>>::get().limits.subject_len;
let subject_len = T::Schedule::get().limits.subject_len;
assert!(subject_len < 1024);
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
@@ -994,8 +986,8 @@ benchmarks! {
// `t`: Number of topics
// `n`: Size of event payload in kb
seal_deposit_event_per_topic_and_kb {
let t in 0 .. <CurrentSchedule<T>>::get().limits.event_topics;
let n in 0 .. T::MaxValueSize::get() / 1024;
let t in 0 .. T::Schedule::get().limits.event_topics;
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
let mut topics = (0..API_BENCHMARK_BATCH_SIZE)
.map(|n| (n * t..n * t + t).map(|i| T::Hashing::hash_of(&i)).collect::<Vec<_>>().encode())
.peekable();
@@ -1055,6 +1047,31 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
// The size of the supplied message does not influence the weight because as it is never
// processed during on-chain execution: It is only ever read during debugging which happens
// when the contract is called as RPC where weights do not matter.
seal_debug_message {
let r in 0 .. API_BENCHMARK_BATCHES;
let max_bytes = code::max_pages::<T>() * 64 * 1024;
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }),
imported_functions: vec![ImportedFunction {
name: "seal_debug_message",
params: vec![ValueType::I32, ValueType::I32],
return_type: Some(ValueType::I32),
}],
call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[
Instruction::I32Const(0), // value_ptr
Instruction::I32Const(max_bytes as i32), // value_len
Instruction::Call(0),
Instruction::Drop,
])),
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
// Only the overhead of calling the function itself with minimal arguments.
// The contract is a bit more complex because I needs to use different keys in order
// to generate unique storage accesses. However, it is still dominated by the storage
@@ -1091,7 +1108,7 @@ benchmarks! {
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
seal_set_storage_per_kb {
let n in 0 .. T::MaxValueSize::get() / 1024;
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
let key = T::Hashing::hash_of(&1u32).as_ref().to_vec();
let key_len = key.len();
let code = WasmModule::<T>::from(ModuleDefinition {
@@ -1155,7 +1172,7 @@ benchmarks! {
<System<T>>::block_number(),
&mut info,
key.as_slice().try_into().map_err(|e| "Key has wrong length")?,
Some(vec![42; T::MaxValueSize::get() as usize])
Some(vec![42; T::Schedule::get().limits.payload_len as usize])
)
.map_err(|_| "Failed to write to storage during setup.")?;
}
@@ -1210,7 +1227,7 @@ benchmarks! {
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
seal_get_storage_per_kb {
let n in 0 .. T::MaxValueSize::get() / 1024;
let n in 0 .. T::Schedule::get().limits.payload_len / 1024;
let key = T::Hashing::hash_of(&1u32).as_ref().to_vec();
let key_len = key.len();
let code = WasmModule::<T>::from(ModuleDefinition {
@@ -1227,7 +1244,7 @@ benchmarks! {
},
DataSegment {
offset: key_len as u32,
value: T::MaxValueSize::get().to_le_bytes().into(),
value: T::Schedule::get().limits.payload_len.to_le_bytes().into(),
},
],
call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[
@@ -1363,7 +1380,7 @@ benchmarks! {
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
seal_call_per_code_transfer_input_output_kb {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let t in 0 .. 1;
let i in 0 .. code::max_pages::<T>() * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1560,7 +1577,7 @@ benchmarks! {
}
seal_instantiate_per_code_input_output_salt_kb {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let i in 0 .. (code::max_pages::<T>() - 1) * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
let s in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1927,7 +1944,7 @@ benchmarks! {
// w_br_table_per_entry = w_bench
instr_br_table_per_entry {
let e in 1 .. <CurrentSchedule<T>>::get().limits.br_table_size;
let e in 1 .. T::Schedule::get().limits.br_table_size;
let entry: Vec<u32> = [0, 1].iter()
.cloned()
.cycle()
@@ -1983,7 +2000,7 @@ benchmarks! {
// w_call_indrect = w_bench - 3 * w_param
instr_call_indirect {
let r in 0 .. INSTR_BENCHMARK_BATCHES;
let num_elements = <CurrentSchedule<T>>::get().limits.table_size;
let num_elements = T::Schedule::get().limits.table_size;
use self::code::TableSegment;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
// We need to make use of the stack here in order to trigger stack height
@@ -2013,8 +2030,8 @@ benchmarks! {
// linearly depend on the amount of parameters to this function.
// Please note that this is not necessary with a direct call.
instr_call_indirect_per_param {
let p in 0 .. <CurrentSchedule<T>>::get().limits.parameters;
let num_elements = <CurrentSchedule<T>>::get().limits.table_size;
let p in 0 .. T::Schedule::get().limits.parameters;
let num_elements = T::Schedule::get().limits.table_size;
use self::code::TableSegment;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
// We need to make use of the stack here in order to trigger stack height
@@ -2044,7 +2061,7 @@ benchmarks! {
// w_local_get = w_bench - 1 * w_param
instr_local_get {
let r in 0 .. INSTR_BENCHMARK_BATCHES;
let max_locals = <CurrentSchedule<T>>::get().limits.stack_height;
let max_locals = T::Schedule::get().limits.stack_height;
let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
RandomGetLocal(0, max_locals),
Regular(Instruction::Drop),
@@ -2061,7 +2078,7 @@ benchmarks! {
// w_local_set = w_bench - 1 * w_param
instr_local_set {
let r in 0 .. INSTR_BENCHMARK_BATCHES;
let max_locals = <CurrentSchedule<T>>::get().limits.stack_height;
let max_locals = T::Schedule::get().limits.stack_height;
let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
RandomI64Repeated(1),
RandomSetLocal(0, max_locals),
@@ -2078,7 +2095,7 @@ benchmarks! {
// w_local_tee = w_bench - 2 * w_param
instr_local_tee {
let r in 0 .. INSTR_BENCHMARK_BATCHES;
let max_locals = <CurrentSchedule<T>>::get().limits.stack_height;
let max_locals = T::Schedule::get().limits.stack_height;
let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
RandomI64Repeated(1),
RandomTeeLocal(0, max_locals),
@@ -2096,7 +2113,7 @@ benchmarks! {
// w_global_get = w_bench - 1 * w_param
instr_global_get {
let r in 0 .. INSTR_BENCHMARK_BATCHES;
let max_globals = <CurrentSchedule<T>>::get().limits.globals;
let max_globals = T::Schedule::get().limits.globals;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
RandomGetGlobal(0, max_globals),
@@ -2112,7 +2129,7 @@ benchmarks! {
// w_global_set = w_bench - 1 * w_param
instr_global_set {
let r in 0 .. INSTR_BENCHMARK_BATCHES;
let max_globals = <CurrentSchedule<T>>::get().limits.globals;
let max_globals = T::Schedule::get().limits.globals;
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
RandomI64Repeated(1),
+161 -29
View File
@@ -315,6 +315,16 @@ pub trait Ext: sealing::Sealed {
/// Get a mutable reference to the nested gas meter.
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
/// Append a string to the debug buffer.
///
/// It is added as-is without any additional new line.
///
/// This is a no-op if debug message recording is disabled which is always the case
/// when the code is executing on-chain.
///
/// Returns `true` if debug message recording is enabled. Otherwise `false` is returned.
fn append_debug_buffer(&mut self, msg: &str) -> bool;
}
/// Describes the different functions that can be exported by an [`Executable`].
@@ -434,6 +444,11 @@ pub struct Stack<'a, T: Config, E> {
frames: SmallVec<T::CallStack>,
/// Statically guarantee that each call stack has at least one frame.
first_frame: Frame<T>,
/// A text buffer used to output human readable information.
///
/// All the bytes added to this field should be valid UTF-8. The buffer has no defined
/// structure and is intended to be shown to users as-is for debugging purposes.
debug_message: Option<&'a mut Vec<u8>>,
/// No executable is held by the struct but influences its behaviour.
_phantom: PhantomData<E>,
}
@@ -442,6 +457,11 @@ pub struct Stack<'a, T: Config, E> {
///
/// For each nested contract call or instantiate one frame is created. It holds specific
/// information for the said call and caches the in-storage `ContractInfo` data structure.
///
/// # Note
///
/// This is an internal data structure. It is exposed to the public for the sole reason
/// of specifying [`Config::CallStack`].
pub struct Frame<T: Config> {
/// The account id of the executing contract.
account_id: T::AccountId,
@@ -574,6 +594,11 @@ where
{
/// Create an run a new call stack by calling into `dest`.
///
/// # Note
///
/// `debug_message` should only ever be set to `Some` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
///
/// # Return Value
///
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
@@ -584,6 +609,7 @@ where
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
input_data: Vec<u8>,
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
let (mut stack, executable) = Self::new(
FrameArgs::Call{dest, cached_info: None},
@@ -591,12 +617,18 @@ where
gas_meter,
schedule,
value,
debug_message,
)?;
stack.run(executable, input_data)
}
/// Create and run a new call stack by instantiating a new contract.
///
/// # Note
///
/// `debug_message` should only ever be set to `Some` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
///
/// # Return Value
///
/// Result<(NewContractAccountId, ExecReturnValue), ExecError)>
@@ -608,6 +640,7 @@ where
value: BalanceOf<T>,
input_data: Vec<u8>,
salt: &[u8],
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
let (mut stack, executable) = Self::new(
FrameArgs::Instantiate {
@@ -620,6 +653,7 @@ where
gas_meter,
schedule,
value,
debug_message,
).map_err(|(e, _code_len)| e)?;
let account_id = stack.top_frame().account_id.clone();
stack.run(executable, input_data)
@@ -634,6 +668,7 @@ where
gas_meter: &'a mut GasMeter<T>,
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(Self, E), (ExecError, u32)> {
let (first_frame, executable) = Self::new_frame(args, value, gas_meter, 0, &schedule)?;
let stack = Self {
@@ -645,6 +680,7 @@ where
account_counter: None,
first_frame,
frames: Default::default(),
debug_message,
_phantom: Default::default(),
};
@@ -841,6 +877,7 @@ where
// Pop the current frame from the stack and return it in case it needs to interact
// with duplicates that might exist on the stack.
// A `None` means that we are returning from the `first_frame`.
let frame = self.frames.pop();
if let Some(frame) = frame {
@@ -872,6 +909,13 @@ where
}
}
} else {
if let Some(message) = &self.debug_message {
log::debug!(
target: "runtime::contracts",
"Debug Message: {}",
core::str::from_utf8(message).unwrap_or("<Invalid UTF8>"),
);
}
// Write back to the root gas meter.
self.gas_meter.absorb_nested(mem::take(&mut self.first_frame.nested_meter));
// Only gas counter changes are persisted in case of a failure.
@@ -1181,7 +1225,7 @@ where
fn block_number(&self) -> T::BlockNumber { self.block_number }
fn max_value_size(&self) -> u32 {
T::MaxValueSize::get()
T::Schedule::get().limits.payload_len
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
@@ -1199,6 +1243,17 @@ where
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.top_frame_mut().nested_meter
}
fn append_debug_buffer(&mut self, msg: &str) -> bool {
if let Some(buffer) = &mut self.debug_message {
if !msg.is_empty() {
buffer.extend(msg.as_bytes());
}
true
} else {
false
}
}
}
fn deposit_event<T: Config>(
@@ -1241,7 +1296,7 @@ mod tests {
test_utils::{place_contract, set_balance, get_balance},
},
exec::ExportedFunction::*,
Error, Weight, CurrentSchedule,
Error, Weight,
};
use sp_core::Bytes;
use sp_runtime::DispatchError;
@@ -1436,12 +1491,12 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, exec_ch);
assert_matches!(
MockStack::run_call(
ALICE, BOB, &mut gas_meter, &schedule, value, vec![],
ALICE, BOB, &mut gas_meter, &schedule, value, vec![], None,
),
Ok(_)
);
@@ -1487,7 +1542,7 @@ mod tests {
);
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
set_balance(&origin, 100);
let balance = get_balance(&dest);
@@ -1499,6 +1554,7 @@ mod tests {
&schedule,
55,
vec![],
None,
).unwrap();
assert!(!output.0.is_success());
@@ -1548,7 +1604,7 @@ mod tests {
);
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
let result = MockStack::run_call(
@@ -1558,6 +1614,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
let output = result.unwrap();
@@ -1578,8 +1635,8 @@ mod tests {
);
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
place_contract(&dest, return_ch);
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, return_ch);
let result = MockStack::run_call(
origin,
@@ -1588,6 +1645,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
let output = result.unwrap();
@@ -1605,7 +1663,7 @@ mod tests {
// This one tests passing the input data into a contract via call.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, input_data_ch);
let result = MockStack::run_call(
@@ -1615,6 +1673,7 @@ mod tests {
&schedule,
0,
vec![1, 2, 3, 4],
None,
);
assert_matches!(result, Ok(_));
});
@@ -1629,7 +1688,7 @@ mod tests {
// This one tests passing the input data into a contract via instantiate.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let subsistence = Contracts::<Test>::subsistence_threshold();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
@@ -1646,6 +1705,7 @@ mod tests {
subsistence * 3,
vec![1, 2, 3, 4],
&[],
None,
);
assert_matches!(result, Ok(_));
});
@@ -1683,7 +1743,7 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
set_balance(&BOB, 1);
place_contract(&BOB, recurse_ch);
@@ -1694,6 +1754,7 @@ mod tests {
&schedule,
value,
vec![],
None,
);
assert_matches!(result, Ok(_));
@@ -1732,7 +1793,7 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&dest, bob_ch);
place_contract(&CHARLIE, charlie_ch);
@@ -1743,6 +1804,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
assert_matches!(result, Ok(_));
@@ -1771,7 +1833,7 @@ mod tests {
});
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, bob_ch);
place_contract(&CHARLIE, charlie_ch);
@@ -1782,6 +1844,7 @@ mod tests {
&schedule,
0,
vec![],
None,
);
assert_matches!(result, Ok(_));
@@ -1793,7 +1856,7 @@ mod tests {
let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success());
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
@@ -1808,6 +1871,7 @@ mod tests {
0, // <- zero endowment
vec![],
&[],
None,
),
Err(_)
);
@@ -1822,7 +1886,7 @@ mod tests {
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
@@ -1838,6 +1902,7 @@ mod tests {
100,
vec![],
&[],
None,
),
Ok((address, ref output)) if output.data == Bytes(vec![80, 65, 83, 83]) => address
);
@@ -1859,7 +1924,7 @@ mod tests {
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
@@ -1875,6 +1940,7 @@ mod tests {
100,
vec![],
&[],
None,
),
Ok((address, ref output)) if output.data == Bytes(vec![70, 65, 73, 76]) => address
);
@@ -1908,13 +1974,13 @@ mod tests {
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
set_balance(&ALICE, Contracts::<Test>::subsistence_threshold() * 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
MockStack::run_call(
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![],
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![], None,
),
Ok(_)
);
@@ -1958,14 +2024,14 @@ mod tests {
});
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
set_balance(&ALICE, 1000);
set_balance(&BOB, 100);
place_contract(&BOB, instantiator_ch);
assert_matches!(
MockStack::run_call(
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![],
ALICE, BOB, &mut GasMeter::<Test>::new(GAS_LIMIT), &schedule, 20, vec![], None,
),
Ok(_)
);
@@ -1987,7 +2053,7 @@ mod tests {
.existential_deposit(15)
.build()
.execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
terminate_ch, &schedule, &mut gas_meter
@@ -2003,6 +2069,7 @@ mod tests {
100,
vec![],
&[],
None,
),
Err(Error::<Test>::TerminatedInConstructor.into())
);
@@ -2027,7 +2094,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
rent_allowance_ch, &schedule, &mut gas_meter
@@ -2042,6 +2109,7 @@ mod tests {
subsistence * 5,
vec![],
&[],
None,
);
assert_matches!(result, Ok(_));
});
@@ -2060,7 +2128,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
@@ -2071,6 +2139,7 @@ mod tests {
&schedule,
0,
vec![],
None,
).unwrap();
});
}
@@ -2109,7 +2178,7 @@ mod tests {
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 100);
place_contract(&BOB, code_hash);
@@ -2120,6 +2189,7 @@ mod tests {
&schedule,
subsistence * 50,
vec![],
None,
).unwrap();
});
}
@@ -2156,7 +2226,7 @@ mod tests {
// This one tests passing the input data into a contract via call.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
place_contract(&BOB, code_bob);
place_contract(&CHARLIE, code_charlie);
@@ -2167,6 +2237,7 @@ mod tests {
&schedule,
0,
vec![0],
None,
);
assert_matches!(result, Ok(_));
});
@@ -2174,10 +2245,9 @@ mod tests {
#[test]
fn recursive_call_during_constructor_fails() {
let code = MockLoader::insert(Constructor, |ctx, executable| {
let my_hash = <Contracts<Test>>::contract_address(&ALICE, &executable.code_hash, &[]);
let code = MockLoader::insert(Constructor, |ctx, _| {
assert_matches!(
ctx.ext.call(0, my_hash, 0, vec![]),
ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![]),
Err((ExecError{error, ..}, _)) if error == <Error<Test>>::NotCallable.into()
);
exec_success()
@@ -2185,7 +2255,7 @@ mod tests {
// This one tests passing the input data into a contract via instantiate.
ExtBuilder::default().build().execute_with(|| {
let schedule = <CurrentSchedule<Test>>::get();
let schedule = <Test as Config>::Schedule::get();
let subsistence = Contracts::<Test>::subsistence_threshold();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
@@ -2202,8 +2272,70 @@ mod tests {
subsistence * 3,
vec![],
&[],
None,
);
assert_matches!(result, Ok(_));
});
}
#[test]
fn printing_works() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
ctx.ext.append_debug_buffer("This is a test");
ctx.ext.append_debug_buffer("More text");
exec_success()
});
let mut debug_buffer = Vec::new();
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&schedule,
0,
vec![],
Some(&mut debug_buffer),
).unwrap();
});
assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text");
}
#[test]
fn printing_works_on_fail() {
let code_hash = MockLoader::insert(Call, |ctx, _| {
ctx.ext.append_debug_buffer("This is a test");
ctx.ext.append_debug_buffer("More text");
exec_trapped()
});
let mut debug_buffer = Vec::new();
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = <Test as Config>::Schedule::get();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
let result = MockStack::run_call(
ALICE,
BOB,
&mut gas_meter,
&schedule,
0,
vec![],
Some(&mut debug_buffer),
);
assert!(result.is_err());
});
assert_eq!(&String::from_utf8(debug_buffer).unwrap(), "This is a testMore text");
}
}
+67 -93
View File
@@ -59,9 +59,6 @@
//!
//! ### Dispatchable functions
//!
//! * [`Pallet::update_schedule`] -
//! ([Root Origin](https://substrate.dev/docs/en/knowledgebase/runtime/origin) Only) -
//! Set a new [`Schedule`].
//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied wasm binary,
//! optionally transferring
//! some balance. This instantiates a new smart contract account with the supplied code and
@@ -160,6 +157,21 @@ pub mod pallet {
/// Handler for rent payments.
type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// Used to answer contracts' queries regarding the current weight price. This is **not**
/// used to calculate the actual fee and is only for informational purposes.
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
/// Describes the weights of the dispatchables of this module and is also used to
/// construct a default cost schedule.
type WeightInfo: WeightInfo;
/// Type that allows the runtime authors to add new host functions for a contract to call.
type ChainExtension: chain_extension::ChainExtension<Self>;
/// Cost schedule and limits.
#[pallet::constant]
type Schedule: Get<Schedule<Self>>;
/// Number of block delay an extrinsic claim surcharge has.
///
/// When claim surcharge is called by an extrinsic the rent is checked
@@ -217,21 +229,6 @@ pub mod pallet {
/// In other words only the origin called "root contract" is allowed to execute then.
type CallStack: smallvec::Array<Item=Frame<Self>>;
/// The maximum size of a storage value and event payload in bytes.
#[pallet::constant]
type MaxValueSize: Get<u32>;
/// Used to answer contracts' queries regarding the current weight price. This is **not**
/// used to calculate the actual fee and is only for informational purposes.
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
/// Describes the weights of the dispatchables of this module and is also used to
/// construct a default cost schedule.
type WeightInfo: WeightInfo;
/// Type that allows the runtime authors to add new host functions for a contract to call.
type ChainExtension: chain_extension::ChainExtension<Self>;
/// The maximum number of tries that can be queued for deletion.
#[pallet::constant]
type DeletionQueueDepth: Get<u32>;
@@ -239,12 +236,6 @@ pub mod pallet {
/// The maximum amount of weight that can be consumed per block for lazy trie removal.
#[pallet::constant]
type DeletionWeightLimit: Get<Weight>;
/// The maximum length of a contract code in bytes. This limit applies to the instrumented
/// version of the code. Therefore `instantiate_with_code` can fail even when supplying
/// a wasm binary below this maximum size.
#[pallet::constant]
type MaxCodeSize: Get<u32>;
}
#[pallet::pallet]
@@ -277,26 +268,6 @@ pub mod pallet {
T::AccountId: UncheckedFrom<T::Hash>,
T::AccountId: AsRef<[u8]>,
{
/// Updates the schedule for metering contracts.
///
/// The schedule's version cannot be less than the version of the stored schedule.
/// If a schedule does not change the instruction weights the version does not
/// need to be increased. Therefore we allow storing a schedule that has the same
/// version as the stored one.
#[pallet::weight(T::WeightInfo::update_schedule())]
pub fn update_schedule(
origin: OriginFor<T>,
schedule: Schedule<T>
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
if <CurrentSchedule<T>>::get().version > schedule.version {
Err(Error::<T>::InvalidScheduleVersion)?
}
Self::deposit_event(Event::ScheduleUpdated(schedule.version));
CurrentSchedule::put(schedule);
Ok(().into())
}
/// Makes a call to an account, optionally transferring some balance.
///
/// * If the account is a smart-contract account, the associated code will be
@@ -304,7 +275,9 @@ pub mod pallet {
/// * If the account is a regular account, any value will be transferred.
/// * If no account exists and the call value is not less than `existential_deposit`,
/// a regular account will be created and any value will be transferred.
#[pallet::weight(T::WeightInfo::call(T::MaxCodeSize::get() / 1024).saturating_add(*gas_limit))]
#[pallet::weight(T::WeightInfo::call(T::Schedule::get().limits.code_len / 1024)
.saturating_add(*gas_limit)
)]
pub fn call(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
@@ -315,9 +288,9 @@ pub mod pallet {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
let (result, code_len) = match ExecStack::<T, PrefabWasmModule<T>>::run_call(
origin, dest, &mut gas_meter, &schedule, value, data
origin, dest, &mut gas_meter, &schedule, value, data, None,
) {
Ok((output, len)) => (Ok(output), len),
Err((err, len)) => (Err(err), len),
@@ -363,14 +336,14 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let code_len = code.len() as u32;
ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
ensure!(code_len <= T::Schedule::get().limits.code_len, Error::<T>::CodeTooLarge);
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
let executable = PrefabWasmModule::from_code(code, &schedule)?;
let code_len = executable.code_len();
ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
ensure!(code_len <= T::Schedule::get().limits.code_len, Error::<T>::CodeTooLarge);
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt,
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None,
).map(|(_address, output)| output);
gas_meter.into_dispatch_result(
result,
@@ -384,8 +357,10 @@ pub mod pallet {
/// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary
/// must be supplied.
#[pallet::weight(
T::WeightInfo::instantiate(T::MaxCodeSize::get() / 1024, salt.len() as u32 / 1024)
.saturating_add(*gas_limit)
T::WeightInfo::instantiate(
T::Schedule::get().limits.code_len / 1024, salt.len() as u32 / 1024
)
.saturating_add(*gas_limit)
)]
pub fn instantiate(
origin: OriginFor<T>,
@@ -397,11 +372,11 @@ pub mod pallet {
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?;
let code_len = executable.code_len();
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt,
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None,
).map(|(_address, output)| output);
gas_meter.into_dispatch_result(
result,
@@ -418,7 +393,7 @@ pub mod pallet {
///
/// If contract is not evicted as a result of this call, [`Error::ContractNotEvictable`]
/// is returned and the sender is not eligible for the reward.
#[pallet::weight(T::WeightInfo::claim_surcharge(T::MaxCodeSize::get() / 1024))]
#[pallet::weight(T::WeightInfo::claim_surcharge(T::Schedule::get().limits.code_len / 1024))]
pub fn claim_surcharge(
origin: OriginFor<T>,
dest: T::AccountId,
@@ -614,12 +589,10 @@ pub mod pallet {
///
/// This can be triggered by a call to `seal_terminate` or `seal_restore_to`.
TerminatedInConstructor,
/// The debug message specified to `seal_debug_message` does contain invalid UTF-8.
DebugMessageInvalidUTF8,
}
/// Current cost schedule for contracts.
#[pallet::storage]
pub(crate) type CurrentSchedule<T: Config> = StorageValue<_, Schedule<T>, ValueQuery>;
/// A mapping from an original code hash to the original code, untouched by instrumentation.
#[pallet::storage]
pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, CodeHash<T>, Vec<u8>>;
@@ -644,29 +617,6 @@ pub mod pallet {
/// stored in said trie. Therefore this operation is performed lazily in `on_initialize`.
#[pallet::storage]
pub(crate) type DeletionQueue<T: Config> = StorageValue<_, Vec<DeletedContract>, ValueQuery>;
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
#[doc = "Current cost schedule for contracts."]
pub current_schedule: Schedule<T>,
}
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self {
current_schedule: Default::default(),
}
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
<CurrentSchedule<T>>::put(&self.current_schedule);
}
}
}
impl<T: Config> Pallet<T>
@@ -678,6 +628,12 @@ where
/// This function is similar to [`Self::call`], but doesn't perform any address lookups
/// and better suitable for calling directly from Rust.
///
/// # Note
///
/// `debug` should only ever be set to `true` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
/// If set to `true` it returns additional human readable debugging information.
///
/// It returns the execution result and the amount of used weight.
pub fn bare_call(
origin: T::AccountId,
@@ -685,17 +641,22 @@ where
value: BalanceOf<T>,
gas_limit: Weight,
input_data: Vec<u8>,
debug: bool,
) -> ContractExecResult {
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
let mut debug_message = if debug {
Some(Vec::new())
} else {
None
};
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
origin, dest, &mut gas_meter, &schedule, value, input_data,
origin, dest, &mut gas_meter, &schedule, value, input_data, debug_message.as_mut(),
);
let gas_consumed = gas_meter.gas_spent();
ContractExecResult {
result: result.map(|r| r.0).map_err(|r| r.0.error),
gas_consumed,
debug_message: Bytes(Vec::new()),
gas_consumed: gas_meter.gas_spent(),
debug_message: debug_message.unwrap_or_default(),
}
}
@@ -709,6 +670,12 @@ where
/// If `compute_projection` is set to `true` the result also contains the rent projection.
/// This is optional because some non trivial and stateful work is performed to compute
/// the projection. See [`Self::rent_projection`].
///
/// # Note
///
/// `debug` should only ever be set to `true` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
/// If set to `true` it returns additional human readable debugging information.
pub fn bare_instantiate(
origin: T::AccountId,
endowment: BalanceOf<T>,
@@ -717,9 +684,10 @@ where
data: Vec<u8>,
salt: Vec<u8>,
compute_projection: bool,
debug: bool,
) -> ContractInstantiateResult<T::AccountId, T::BlockNumber> {
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
let executable = match code {
Code::Upload(Bytes(binary)) => PrefabWasmModule::from_code(binary, &schedule),
Code::Existing(hash) => PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter),
@@ -729,11 +697,17 @@ where
Err(error) => return ContractInstantiateResult {
result: Err(error.into()),
gas_consumed: gas_meter.gas_spent(),
debug_message: Bytes(Vec::new()),
debug_message: Vec::new(),
}
};
let mut debug_message = if debug {
Some(Vec::new())
} else {
None
};
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt,
origin, executable, &mut gas_meter, &schedule,
endowment, data, &salt, debug_message.as_mut(),
).and_then(|(account_id, result)| {
let rent_projection = if compute_projection {
Some(Rent::<T, PrefabWasmModule<T>>::compute_projection(&account_id)
@@ -751,7 +725,7 @@ where
ContractInstantiateResult {
result: result.map_err(|e| e.error),
gas_consumed: gas_meter.gas_spent(),
debug_message: Bytes(Vec::new()),
debug_message: debug_message.unwrap_or_default(),
}
}
@@ -822,7 +796,7 @@ where
/// Store code for benchmarks which does not check nor instrument the code.
#[cfg(feature = "runtime-benchmarks")]
fn store_code_raw(code: Vec<u8>) -> frame_support::dispatch::DispatchResult {
let schedule = <CurrentSchedule<T>>::get();
let schedule = T::Schedule::get();
PrefabWasmModule::store_code_unchecked(code, &schedule)?;
Ok(())
}
+11 -12
View File
@@ -15,24 +15,23 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::{Config, Weight, CurrentSchedule, Pallet, Schedule};
use frame_support::traits::{GetPalletVersion, PalletVersion, Get};
use crate::{Config, Weight, Pallet};
use frame_support::{
storage::migration,
traits::{GetPalletVersion, PalletVersion, PalletInfoAccess, Get},
};
pub fn migrate<T: Config>() -> Weight {
let mut weight: Weight = 0;
match <Pallet<T>>::storage_version() {
// Replace the schedule with the new default and increment its version.
Some(version) if version == PalletVersion::new(3, 0, 0) => {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
let _ = <CurrentSchedule<T>>::translate::<u32, _>(|version| {
version.map(|version| Schedule {
version: version.saturating_add(1),
// Default limits were not decreased. Therefore it is OK to overwrite
// the schedule with the new defaults.
.. Default::default()
})
});
weight = weight.saturating_add(T::DbWeight::get().writes(1));
migration::remove_storage_prefix(
<Pallet<T>>::name().as_bytes(),
b"CurrentSchedule",
b"",
);
}
_ => (),
}
+46 -35
View File
@@ -42,27 +42,12 @@ pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 1_000;
/// Definition of the cost schedule and other parameterizations for the wasm vm.
///
/// Its fields are private to the crate in order to allow addition of new contract
/// callable functions without bumping to a new major version. A genesis config should
/// rely on public functions of this type.
/// callable functions without bumping to a new major version. The supplied [`Config::Schedule`]
/// should rely on public functions of this type.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug)]
pub struct Schedule<T: Config> {
/// Version of the schedule.
///
/// # Note
///
/// Must be incremented whenever the [`self.instruction_weights`] are changed. The
/// reason is that changes to instruction weights require a re-instrumentation
/// of all contracts which are triggered by a version comparison on call.
/// Changes to other parts of the schedule should not increment the version in
/// order to avoid unnecessary re-instrumentations.
pub(crate) version: u32,
/// Whether the `seal_println` function is allowed to be used contracts.
/// MUST only be enabled for `dev` chains, NOT for production chains
pub(crate) enable_println: bool,
/// Describes the upper limits on various metrics.
pub(crate) limits: Limits,
@@ -73,12 +58,31 @@ pub struct Schedule<T: Config> {
pub(crate) host_fn_weights: HostFnWeights<T>,
}
impl<T: Config> Schedule<T> {
/// Set the version of the instruction weights.
///
/// # Note
///
/// Should be incremented whenever any instruction weight is changed. The
/// reason is that changes to instruction weights require a re-instrumentation
/// in order to apply the changes to an already deployed code. The re-instrumentation
/// is triggered by comparing the version of the current schedule with the the code was
/// instrumented with. Changes usually happen when pallet_contracts is re-benchmarked.
///
/// Changes to other parts of the schedule should not increment the version in
/// order to avoid unnecessary re-instrumentations.
pub fn set_instruction_weights_version(&mut self, version: u32) {
self.instruction_weights.version = version;
}
}
/// Describes the upper limits on various metrics.
///
/// # Note
///
/// The values in this struct should only ever be increased for a deployed chain. The reason
/// is that decreasing those values will break existing contracts which are above the new limits.
/// The values in this struct should never be decreased. The reason is that decreasing those
/// values will break existing contracts which are above the new limits when a
/// re-instrumentation is triggered.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Limits {
@@ -121,6 +125,17 @@ pub struct Limits {
/// The maximum length of a subject in bytes used for PRNG generation.
pub subject_len: u32,
/// The maximum nesting level of the call stack.
pub call_depth: u32,
/// The maximum size of a storage value and event payload in bytes.
pub payload_len: u32,
/// The maximum length of a contract code in bytes. This limit applies to the instrumented
/// version of the code. Therefore `instantiate_with_code` can fail even when supplying
/// a wasm binary below this maximum size.
pub code_len: u32,
}
impl Limits {
@@ -153,6 +168,10 @@ impl Limits {
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, WeightDebug)]
pub struct InstructionWeights<T: Config> {
/// Version of the instruction weights.
///
/// See [`Schedule::set_instruction_weights_version`].
pub(crate) version: u32,
pub i64const: u32,
pub i64load: u32,
pub i64store: u32,
@@ -291,6 +310,9 @@ pub struct HostFnWeights<T: Config> {
/// Weight per byte of an event deposited through `seal_deposit_event`.
pub deposit_event_per_byte: Weight,
/// Weight of calling `seal_debug_message`.
pub debug_message: Weight,
/// Weight of calling `seal_set_rent_allowance`.
pub set_rent_allowance: Weight,
@@ -454,8 +476,6 @@ macro_rules! cost_byte_batched {
impl<T: Config> Default for Schedule<T> {
fn default() -> Self {
Self {
version: 0,
enable_println: false,
limits: Default::default(),
instruction_weights: Default::default(),
host_fn_weights: Default::default(),
@@ -476,6 +496,9 @@ impl Default for Limits {
table_size: 4096,
br_table_size: 256,
subject_len: 32,
call_depth: 32,
payload_len: 16 * 1024,
code_len: 128 * 1024,
}
}
}
@@ -484,6 +507,7 @@ impl<T: Config> Default for InstructionWeights<T> {
fn default() -> Self {
let max_pages = Limits::default().memory_pages;
Self {
version: 2,
i64const: cost_instr!(instr_i64const, 1),
i64load: cost_instr!(instr_i64load, 2),
i64store: cost_instr!(instr_i64store, 2),
@@ -569,6 +593,7 @@ impl<T: Config> Default for HostFnWeights<T> {
deposit_event: cost_batched!(seal_deposit_event),
deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0),
deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1),
debug_message: cost_batched!(seal_debug_message),
set_rent_allowance: cost_batched!(seal_set_rent_allowance),
set_storage: cost_batched!(seal_set_storage),
set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb),
@@ -606,20 +631,6 @@ struct ScheduleRules<'a, T: Config> {
}
impl<T: Config> Schedule<T> {
/// Allow contracts to call `seal_println` in order to print messages to the console.
///
/// This should only ever be activated in development chains. The printed messages
/// can be observed on the console by setting the environment variable
/// `RUST_LOG=runtime=debug` when running the node.
///
/// # Note
///
/// Is set to `false` by default.
pub fn enable_println(mut self, enable: bool) -> Self {
self.enable_println = enable;
self
}
pub(crate) fn rules(&self, module: &elements::Module) -> impl rules::Rules + '_ {
ScheduleRules {
schedule: &self,
+131 -17
View File
@@ -40,7 +40,7 @@ use sp_io::hashing::blake2_256;
use frame_support::{
assert_ok, assert_err, assert_err_ignore_postinfo,
parameter_types, assert_storage_noop,
traits::{Currency, ReservableCurrency, OnInitialize, GenesisBuild},
traits::{Currency, ReservableCurrency, OnInitialize},
weights::{Weight, PostDispatchInfo, DispatchClass, constants::WEIGHT_PER_SECOND},
dispatch::DispatchErrorWithPostInfo,
storage::child,
@@ -63,7 +63,7 @@ frame_support::construct_runtime!(
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
Randomness: pallet_randomness_collective_flip::{Pallet, Call, Storage},
Contracts: pallet_contracts::{Pallet, Call, Config<T>, Storage, Event<T>},
Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
}
);
@@ -265,6 +265,7 @@ parameter_types! {
pub const DeletionQueueDepth: u32 = 1024;
pub const DeletionWeightLimit: Weight = 500_000_000_000;
pub const MaxCodeSize: u32 = 2 * 1024;
pub MySchedule: Schedule<Test> = <Schedule<Test>>::default();
}
parameter_types! {
@@ -291,13 +292,12 @@ impl Config for Test {
type RentFraction = RentFraction;
type SurchargeReward = SurchargeReward;
type CallStack = [Frame<Self>; 31];
type MaxValueSize = MaxValueSize;
type WeightPrice = Self;
type WeightInfo = ();
type ChainExtension = TestExtension;
type DeletionQueueDepth = DeletionQueueDepth;
type DeletionWeightLimit = DeletionWeightLimit;
type MaxCodeSize = MaxCodeSize;
type Schedule = MySchedule;
}
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
@@ -331,12 +331,6 @@ impl ExtBuilder {
pallet_balances::GenesisConfig::<Test> {
balances: vec![],
}.assimilate_storage(&mut t).unwrap();
pallet_contracts::GenesisConfig {
current_schedule: Schedule::<Test> {
enable_println: true,
..Default::default()
},
}.assimilate_storage(&mut t).unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
@@ -564,7 +558,7 @@ fn deposit_event_max_value_limit() {
addr.clone(),
0,
GAS_LIMIT * 2, // we are copying a huge buffer,
<Test as Config>::MaxValueSize::get().encode(),
<Test as Config>::Schedule::get().limits.payload_len.encode(),
));
// Call contract with too large a storage value.
@@ -574,7 +568,7 @@ fn deposit_event_max_value_limit() {
addr,
0,
GAS_LIMIT,
(<Test as Config>::MaxValueSize::get() + 1).encode(),
(<Test as Config>::Schedule::get().limits.payload_len + 1).encode(),
),
Error::<Test>::ValueTooLarge,
);
@@ -1544,7 +1538,7 @@ fn storage_max_value_limit() {
addr.clone(),
0,
GAS_LIMIT * 2, // we are copying a huge buffer
<Test as Config>::MaxValueSize::get().encode(),
<Test as Config>::Schedule::get().limits.payload_len.encode(),
));
// Call contract with too large a storage value.
@@ -1554,7 +1548,7 @@ fn storage_max_value_limit() {
addr,
0,
GAS_LIMIT,
(<Test as Config>::MaxValueSize::get() + 1).encode(),
(<Test as Config>::Schedule::get().limits.payload_len + 1).encode(),
),
Error::<Test>::ValueTooLarge,
);
@@ -1896,6 +1890,7 @@ fn crypto_hashes() {
0,
GAS_LIMIT,
params,
false,
).result.unwrap();
assert!(result.is_success());
let expected = hash_fn(input.as_ref());
@@ -1931,6 +1926,7 @@ fn transfer_return_code() {
0,
GAS_LIMIT,
vec![],
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
@@ -1945,6 +1941,7 @@ fn transfer_return_code() {
0,
GAS_LIMIT,
vec![],
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
});
@@ -1979,6 +1976,7 @@ fn call_return_code() {
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&DJANGO).to_vec(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::NotCallable);
@@ -2002,6 +2000,7 @@ fn call_return_code() {
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&0u32.to_le_bytes()).cloned().collect(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
@@ -2016,6 +2015,7 @@ fn call_return_code() {
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&0u32.to_le_bytes()).cloned().collect(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
@@ -2027,6 +2027,7 @@ fn call_return_code() {
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&1u32.to_le_bytes()).cloned().collect(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
@@ -2037,6 +2038,7 @@ fn call_return_code() {
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&2u32.to_le_bytes()).cloned().collect(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
@@ -2084,6 +2086,7 @@ fn instantiate_return_code() {
0,
GAS_LIMIT,
callee_hash.clone(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
@@ -2098,6 +2101,7 @@ fn instantiate_return_code() {
0,
GAS_LIMIT,
callee_hash.clone(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
@@ -2109,6 +2113,7 @@ fn instantiate_return_code() {
0,
GAS_LIMIT,
vec![0; 33],
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CodeNotFound);
@@ -2119,6 +2124,7 @@ fn instantiate_return_code() {
0,
GAS_LIMIT,
callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
@@ -2129,6 +2135,7 @@ fn instantiate_return_code() {
0,
GAS_LIMIT,
callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(),
false,
).result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
@@ -2216,6 +2223,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
vec![0, 99],
false,
);
let gas_consumed = result.gas_consumed;
assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]);
@@ -2228,6 +2236,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
vec![1],
false,
).result.unwrap();
// those values passed in the fixture
assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12));
@@ -2239,6 +2248,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
vec![2, 42],
false,
);
assert_ok!(result.result);
assert_eq!(result.gas_consumed, gas_consumed + 42);
@@ -2250,6 +2260,7 @@ fn chain_extension_works() {
0,
GAS_LIMIT,
vec![3],
false,
).result.unwrap();
assert_eq!(result.flags, ReturnFlags::REVERT);
assert_eq!(result.data, Bytes(vec![42, 99]));
@@ -2782,6 +2793,7 @@ fn reinstrument_does_charge() {
0,
GAS_LIMIT,
zero.clone(),
false,
);
assert!(result0.result.unwrap().is_success());
@@ -2791,15 +2803,17 @@ fn reinstrument_does_charge() {
0,
GAS_LIMIT,
zero.clone(),
false,
);
assert!(result1.result.unwrap().is_success());
// They should match because both where called with the same schedule.
assert_eq!(result0.gas_consumed, result1.gas_consumed);
// Update the schedule version but keep the rest the same
crate::CurrentSchedule::mutate(|old: &mut Schedule<Test>| {
old.version += 1;
// We cannot change the schedule. Instead, we decrease the version of the deployed
// contract below the current schedule's version.
crate::CodeStorage::mutate(&code_hash, |code: &mut Option<PrefabWasmModule<Test>>| {
code.as_mut().unwrap().decrement_version();
});
// This call should trigger reinstrumentation
@@ -2809,6 +2823,7 @@ fn reinstrument_does_charge() {
0,
GAS_LIMIT,
zero.clone(),
false,
);
assert!(result2.result.unwrap().is_success());
assert!(result2.gas_consumed > result1.gas_consumed);
@@ -2818,3 +2833,102 @@ fn reinstrument_does_charge() {
);
});
}
#[test]
fn debug_message_works() {
let (wasm, code_hash) = compile_module::<Test>("debug_message_works").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(
Contracts::instantiate_with_code(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
wasm,
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let result = Contracts::bare_call(
ALICE,
addr,
0,
GAS_LIMIT,
vec![],
true,
);
assert_matches!(result.result, Ok(_));
assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!");
});
}
#[test]
fn debug_message_logging_disabled() {
let (wasm, code_hash) = compile_module::<Test>("debug_message_logging_disabled").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(
Contracts::instantiate_with_code(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
wasm,
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// disable logging by passing `false`
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![],
false,
);
assert_matches!(result.result, Ok(_));
// the dispatchables always run without debugging
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr,
0,
GAS_LIMIT,
vec![],
));
assert!(result.debug_message.is_empty());
});
}
#[test]
fn debug_message_invalid_utf8() {
let (wasm, code_hash) = compile_module::<Test>("debug_message_invalid_utf8").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(
Contracts::instantiate_with_code(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
wasm,
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let result = Contracts::bare_call(
ALICE,
addr,
0,
GAS_LIMIT,
vec![],
true,
);
assert_err!(result.result, <Error<Test>>::DebugMessageInvalidUTF8);
});
}
@@ -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(())
}
+85 -6
View File
@@ -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,
})
);
}
}
+2 -40
View File
@@ -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 {
+40 -12
View File
@@ -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.
File diff suppressed because it is too large Load Diff