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