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
+2 -11
View File
@@ -22,7 +22,7 @@ use sc_chain_spec::ChainSpecExtension;
use sp_core::{Pair, Public, crypto::UncheckedInto, sr25519};
use serde::{Serialize, Deserialize};
use node_runtime::{
AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, ContractsConfig, CouncilConfig,
AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, CouncilConfig,
DemocracyConfig, GrandpaConfig, ImOnlineConfig, SessionConfig, SessionKeys, StakerStatus,
StakingConfig, ElectionsConfig, IndicesConfig, SocietyConfig, SudoConfig, SystemConfig,
TechnicalCommitteeConfig, wasm_binary_unwrap, MAX_NOMINATIONS,
@@ -146,7 +146,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
let endowed_accounts: Vec<AccountId> = vec![root_key.clone()];
testnet_genesis(initial_authorities, vec![], root_key, Some(endowed_accounts), false)
testnet_genesis(initial_authorities, vec![], root_key, Some(endowed_accounts))
}
/// Staging testnet config.
@@ -212,7 +212,6 @@ pub fn testnet_genesis(
initial_nominators: Vec<AccountId>,
root_key: AccountId,
endowed_accounts: Option<Vec<AccountId>>,
enable_println: bool,
) -> GenesisConfig {
let mut endowed_accounts: Vec<AccountId> = endowed_accounts.unwrap_or_else(|| {
vec![
@@ -308,11 +307,6 @@ pub fn testnet_genesis(
.collect(),
phantom: Default::default(),
},
pallet_contracts: ContractsConfig {
// println should only be enabled on development chains
current_schedule: pallet_contracts::Schedule::default()
.enable_println(enable_println),
},
pallet_sudo: SudoConfig {
key: root_key,
},
@@ -352,7 +346,6 @@ fn development_config_genesis() -> GenesisConfig {
vec![],
get_account_id_from_seed::<sr25519::Public>("Alice"),
None,
true,
)
}
@@ -380,7 +373,6 @@ fn local_testnet_genesis() -> GenesisConfig {
vec![],
get_account_id_from_seed::<sr25519::Public>("Alice"),
None,
false,
)
}
@@ -414,7 +406,6 @@ pub(crate) mod tests {
vec![],
get_account_id_from_seed::<sr25519::Public>("Alice"),
None,
false,
)
}
+5 -6
View File
@@ -793,7 +793,7 @@ parameter_types! {
<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
)) / 5) as u32;
pub MaxCodeSize: u32 = 128 * 1024;
pub Schedule: pallet_contracts::Schedule<Runtime> = Default::default();
}
impl pallet_contracts::Config for Runtime {
@@ -810,13 +810,12 @@ impl pallet_contracts::Config for Runtime {
type RentFraction = RentFraction;
type SurchargeReward = SurchargeReward;
type CallStack = [pallet_contracts::Frame<Self>; 31];
type MaxValueSize = MaxValueSize;
type WeightPrice = pallet_transaction_payment::Module<Self>;
type WeightInfo = pallet_contracts::weights::SubstrateWeight<Self>;
type ChainExtension = ();
type DeletionQueueDepth = DeletionQueueDepth;
type DeletionWeightLimit = DeletionWeightLimit;
type MaxCodeSize = MaxCodeSize;
type Schedule = Schedule;
}
impl pallet_sudo::Config for Runtime {
@@ -1114,7 +1113,7 @@ construct_runtime!(
TechnicalMembership: pallet_membership::<Instance1>::{Pallet, Call, Storage, Event<T>, Config<T>},
Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned},
Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event<T>},
Contracts: pallet_contracts::{Pallet, Call, Config<T>, Storage, Event<T>},
Contracts: pallet_contracts::{Pallet, Call, Storage, Event<T>},
Sudo: pallet_sudo::{Pallet, Call, Config<T>, Storage, Event<T>},
ImOnline: pallet_im_online::{Pallet, Call, Storage, Event<T>, ValidateUnsigned, Config<T>},
AuthorityDiscovery: pallet_authority_discovery::{Pallet, Call, Config},
@@ -1354,7 +1353,7 @@ impl_runtime_apis! {
gas_limit: u64,
input_data: Vec<u8>,
) -> pallet_contracts_primitives::ContractExecResult {
Contracts::bare_call(origin, dest, value, gas_limit, input_data)
Contracts::bare_call(origin, dest, value, gas_limit, input_data, true)
}
fn instantiate(
@@ -1366,7 +1365,7 @@ impl_runtime_apis! {
salt: Vec<u8>,
) -> pallet_contracts_primitives::ContractInstantiateResult<AccountId, BlockNumber>
{
Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true)
Contracts::bare_instantiate(origin, endowment, gas_limit, code, data, salt, true, true)
}
fn get_storage(
+1 -4
View File
@@ -22,7 +22,7 @@ use crate::keyring::*;
use sp_keyring::{Ed25519Keyring, Sr25519Keyring};
use node_runtime::{
GenesisConfig, BalancesConfig, SessionConfig, StakingConfig, SystemConfig,
GrandpaConfig, IndicesConfig, ContractsConfig, SocietyConfig, wasm_binary_unwrap,
GrandpaConfig, IndicesConfig, SocietyConfig, wasm_binary_unwrap,
AccountId, StakerStatus, BabeConfig, BABE_GENESIS_EPOCH_CONFIG,
};
use node_runtime::constants::currency::*;
@@ -97,9 +97,6 @@ pub fn config_endowed(
invulnerables: vec![alice(), bob(), charlie()],
.. Default::default()
},
pallet_contracts: ContractsConfig {
current_schedule: Default::default(),
},
pallet_babe: BabeConfig {
authorities: vec![],
epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG),
@@ -106,14 +106,11 @@ fn genesis_constructor(
.map(chain_spec::authority_keys_from_seed)
.collect::<Vec<_>>();
let enable_println = true;
chain_spec::testnet_genesis(
authorities,
nominator_accounts.to_vec(),
sudo_account.clone(),
Some(endowed_accounts.to_vec()),
enable_println,
)
}
+4
View File
@@ -20,7 +20,11 @@ In other words: Upgrading this pallet will not break pre-existing contracts.
### Added
- Replaced `seal_println` with `seal_debug_message` which allows output to an RPC client.
[1](https://github.com/paritytech/substrate/pull/8773)
- Add new `instantiate` RPC that allows clients to dry-run contract instantiation.
[1](https://github.com/paritytech/substrate/pull/8451)
- Make storage and fields of `Schedule` private to the crate.
[1](https://github.com/paritytech/substrate/pull/8359)
+16 -7
View File
@@ -59,17 +59,26 @@ writing WebAssembly based smart contracts in the Rust programming language.
## Debugging
Contracts can emit messages to the node console when run on a development chain through the
`seal_println` API. This is exposed in ink! via
Contracts can emit messages to the client when called as RPC through the `seal_debug_message`
API. This is exposed in ink! via
[`ink_env::debug_println()`](https://docs.rs/ink_env/latest/ink_env/fn.debug_println.html).
In order to see these messages the log level for the `runtime::contracts` target needs to be raised
to at least the `info` level which is the default. However, those messages are easy to overlook
because of the noise generated by block production. A good starting point for contract debugging
could be:
Those messages are gathered into an internal buffer and send to the RPC client.
It is up the the individual client if and how those messages are presented to the user.
This buffer is also printed as a debug message. In order to see these messages on the node
console the log level for the `runtime::contracts` target needs to be raised to at least
the `debug` level. However, those messages are easy to overlook because of the noise generated
by block production. A good starting point for observing them on the console is:
```bash
cargo run --release -- --dev --tmp -lerror,runtime::contracts
cargo run --release -- --dev --tmp -lerror,runtime::contracts=debug
```
This raises the log level of `runtime::contracts` to `debug` and all other targets
to `error` in order to prevent them from spamming the console.
`--dev`: Use a dev chain spec
`--tmp`: Use temporary storage for chain data (the chain state is deleted on exit)
License: Apache-2.0
+25 -5
View File
@@ -37,18 +37,22 @@ use serde::{Serialize, Deserialize};
pub struct ContractResult<T> {
/// How much gas was consumed during execution.
pub gas_consumed: u64,
/// An optional debug message. This message is only non-empty when explicitly requested
/// by the code that calls into the contract.
/// An optional debug message. This message is only filled when explicitly requested
/// by the code that calls into the contract. Otherwise it is empty.
///
/// The contained bytes are valid UTF-8. This is not declared as `String` because
/// this type is not allowed within the runtime. A client should decode them in order
/// to present the message to its users.
/// this type is not allowed within the runtime.
///
/// Clients should not make any assumptions about the format of the buffer.
/// They should just display it as-is. It is **not** only a collection of log lines
/// provided by a contract but a formatted buffer with different sections.
///
/// # Note
///
/// The debug message is never generated during on-chain execution. It is reserved for
/// RPC calls.
pub debug_message: Bytes,
#[cfg_attr(feature = "std", serde(with = "as_string"))]
pub debug_message: Vec<u8>,
/// The execution result of the wasm code.
pub result: T,
}
@@ -146,3 +150,19 @@ pub enum Code<Hash> {
/// The code hash of an on-chain wasm blob.
Existing(Hash),
}
#[cfg(feature = "std")]
mod as_string {
use super::*;
use serde::{Serializer, Deserializer, ser::Error};
pub fn serialize<S: Serializer>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error> {
std::str::from_utf8(bytes)
.map_err(|e| S::Error::custom(format!("Debug buffer contains invalid UTF8: {}", e)))?
.serialize(serializer)
}
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
Ok(String::deserialize(deserializer)?.into_bytes())
}
}
@@ -5,7 +5,6 @@
(import "seal0" "seal_instantiate" (func $seal_instantiate
(param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
))
(import "seal0" "seal_println" (func $seal_println (param i32 i32)))
(import "env" "memory" (memory 1 1))
(func $assert (param i32)
@@ -0,0 +1,18 @@
;; Emit a "Hello World!" debug message
(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 12) ;; The size of the buffer
)
;; the above call traps because we supplied invalid utf8
unreachable
)
(func (export "deploy"))
)
@@ -0,0 +1,28 @@
;; Emit a "Hello World!" debug message but assume that logging is disabled.
(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 $assert_eq (param i32 i32)
(block $ok
(br_if $ok
(i32.eq (get_local 0) (get_local 1))
)
(unreachable)
)
)
(func (export "call")
(call $assert_eq
(call $seal_debug_message
(i32.const 0) ;; Pointer to the text buffer
(i32.const 12) ;; The size of the buffer
)
(i32.const 9) ;; LoggingDisabled return code
)
)
(func (export "deploy"))
)
@@ -0,0 +1,28 @@
;; Emit a "Hello World!" debug message
(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 $assert_eq (param i32 i32)
(block $ok
(br_if $ok
(i32.eq (get_local 0) (get_local 1))
)
(unreachable)
)
)
(func (export "call")
(call $assert_eq
(call $seal_debug_message
(i32.const 0) ;; Pointer to the text buffer
(i32.const 12) ;; The size of the buffer
)
(i32.const 0) ;; success return code
)
)
(func (export "deploy"))
)
@@ -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