mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
contracts: Add seal_rent_status (#8780)
* Move public functions up in rent.rs * Added RentStatus * Fix test name for consistency Co-authored-by: Michael Müller <michi@parity.io> * Mark rent functions as unstable * Add unstable interfaces to README * Fix doc typos Co-authored-by: Andrew Jones <ascjones@gmail.com> * Use DefaultNoBound * Simplify calc_share(1) * Don't output empty debug messages * Make `seal_debug_message` unstable Co-authored-by: Michael Müller <michi@parity.io> Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
committed by
GitHub
parent
f29a6fdad3
commit
0057c0b53f
@@ -237,3 +237,8 @@ try-runtime = [
|
||||
"pallet-vesting/try-runtime",
|
||||
"pallet-gilt/try-runtime",
|
||||
]
|
||||
# Make contract callable functions marked as __unstable__ available. Do not enable
|
||||
# on live chains as those are subject to change.
|
||||
contracts-unstable-interface = [
|
||||
"pallet-contracts/unstable-interface"
|
||||
]
|
||||
|
||||
@@ -72,5 +72,9 @@ runtime-benchmarks = [
|
||||
"frame-benchmarking",
|
||||
"rand",
|
||||
"rand_pcg",
|
||||
"unstable-interface",
|
||||
]
|
||||
try-runtime = ["frame-support/try-runtime"]
|
||||
# Make contract callable functions marked as __unstable__ available. Do not enable
|
||||
# on live chains as those are subject to change.
|
||||
unstable-interface = []
|
||||
|
||||
@@ -69,7 +69,8 @@ It is up the the individual client if and how those messages are presented to th
|
||||
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:
|
||||
by block production. A good starting point for observing them on the console is using this
|
||||
command line in the root directory of the substrate repository:
|
||||
|
||||
```bash
|
||||
cargo run --release -- --dev --tmp -lerror,runtime::contracts=debug
|
||||
@@ -81,4 +82,27 @@ 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)
|
||||
|
||||
## Unstable Interfaces
|
||||
|
||||
Driven by the desire to have an iterative approach in developing new contract interfaces
|
||||
this pallet contains the concept of an unstable interface. Akin to the rust nightly compiler
|
||||
it allows us to add new interfaces but mark them as unstable so that contract languages can
|
||||
experiment with them and give feedback before we stabilize those.
|
||||
|
||||
In order to access interfaces marked as `__unstable__` in `runtime.rs` one need to compile
|
||||
this crate with the `unstable-interface` feature enabled. It should be obvious that any
|
||||
live runtime should never be compiled with this feature: In addition to be subject to
|
||||
change or removal those interfaces do not have proper weights associated with them and
|
||||
are therefore considered unsafe.
|
||||
|
||||
The substrate runtime exposes this feature as `contracts-unstable-interface`. Example
|
||||
commandline for running the substrate node with unstable contracts interfaces:
|
||||
|
||||
```bash
|
||||
cargo run --release --features contracts-unstable-interface -- --dev
|
||||
```
|
||||
|
||||
New interfaces are generally added as unstable and might go through several iterations
|
||||
before they are promoted to a stable interface.
|
||||
|
||||
License: Apache-2.0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
;; Emit a "Hello World!" debug message
|
||||
(module
|
||||
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "\fc")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
;; 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 "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "Hello World!")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
;; Emit a "Hello World!" debug message
|
||||
(module
|
||||
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "Hello World!")
|
||||
|
||||
@@ -103,6 +103,7 @@ impl ImportedMemory {
|
||||
}
|
||||
|
||||
pub struct ImportedFunction {
|
||||
pub module: &'static str,
|
||||
pub name: &'static str,
|
||||
pub params: Vec<ValueType>,
|
||||
pub return_type: Option<ValueType>,
|
||||
@@ -171,7 +172,7 @@ where
|
||||
.build_sig();
|
||||
let sig = contract.push_signature(sig);
|
||||
contract = contract.import()
|
||||
.module("seal0")
|
||||
.module(func.module)
|
||||
.field(func.name)
|
||||
.with_external(parity_wasm::elements::External::Function(sig))
|
||||
.build();
|
||||
@@ -292,6 +293,7 @@ where
|
||||
ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: getter_name,
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -321,6 +323,7 @@ where
|
||||
ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name,
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
|
||||
@@ -527,20 +527,13 @@ benchmarks! {
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
|
||||
|
||||
seal_rent_params {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let instance = Contract::<T>::new(WasmModule::getter(
|
||||
"seal_rent_params", r * API_BENCHMARK_BATCH_SIZE
|
||||
), vec![], Endow::Max)?;
|
||||
let origin = RawOrigin::Signed(instance.caller.clone());
|
||||
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
|
||||
|
||||
seal_weight_to_fee {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let pages = code::max_pages::<T>();
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_weight_to_fee",
|
||||
params: vec![ValueType::I64, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -565,6 +558,7 @@ benchmarks! {
|
||||
let r in 0 .. API_BENCHMARK_BATCHES;
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "gas",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -588,6 +582,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_input",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -616,6 +611,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_input",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -645,6 +641,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_return",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -666,6 +663,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_return",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -692,6 +690,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_terminate",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -729,6 +728,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_terminate",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -780,6 +780,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_restore_to",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -864,6 +865,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_restore_to",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -935,6 +937,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_random",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -965,6 +968,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_deposit_event",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -996,6 +1000,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_deposit_event",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -1026,6 +1031,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_set_rent_allowance",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -1056,6 +1062,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "__unstable__",
|
||||
name: "seal_debug_message",
|
||||
params: vec![ValueType::I32, ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
@@ -1085,6 +1092,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_set_storage",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -1114,6 +1122,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_set_storage",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -1149,6 +1158,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_clear_storage",
|
||||
params: vec![ValueType::I32],
|
||||
return_type: None,
|
||||
@@ -1192,6 +1202,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_get_storage",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
@@ -1233,6 +1244,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_get_storage",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
@@ -1285,6 +1297,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_transfer",
|
||||
params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32],
|
||||
return_type: Some(ValueType::I32),
|
||||
@@ -1336,6 +1349,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_call",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -1387,6 +1401,7 @@ benchmarks! {
|
||||
let callee_code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_return",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -1417,6 +1432,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_call",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -1502,6 +1518,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_instantiate",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -1584,6 +1601,7 @@ benchmarks! {
|
||||
let callee_code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_return",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
@@ -1627,6 +1645,7 @@ benchmarks! {
|
||||
let code = WasmModule::<T>::from(ModuleDefinition {
|
||||
memory: Some(ImportedMemory::max::<T>()),
|
||||
imported_functions: vec![ImportedFunction {
|
||||
module: "seal0",
|
||||
name: "seal_instantiate",
|
||||
params: vec![
|
||||
ValueType::I32,
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
use crate::{
|
||||
CodeHash, Event, Config, Pallet as Contracts,
|
||||
BalanceOf, ContractInfo, gas::GasMeter, rent::Rent, storage::Storage,
|
||||
BalanceOf, ContractInfo, gas::GasMeter, rent::{Rent, RentStatus}, storage::Storage,
|
||||
Error, ContractInfoOf, Schedule, AliveContractInfo, AccountCounter,
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
@@ -32,7 +32,7 @@ use frame_support::{
|
||||
storage::{with_transaction, TransactionOutcome},
|
||||
traits::{ExistenceRequirement, Currency, Time, Randomness, Get},
|
||||
weights::Weight,
|
||||
ensure,
|
||||
ensure, DefaultNoBound,
|
||||
};
|
||||
use pallet_contracts_primitives::{ExecReturnValue};
|
||||
use smallvec::{SmallVec, Array};
|
||||
@@ -82,7 +82,7 @@ impl<T: Into<DispatchError>> From<T> for ExecError {
|
||||
}
|
||||
|
||||
/// Information needed for rent calculations that can be requested by a contract.
|
||||
#[derive(codec::Encode)]
|
||||
#[derive(codec::Encode, DefaultNoBound)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct RentParams<T: Config> {
|
||||
/// The total balance of the contract. Includes the balance transferred from the caller.
|
||||
@@ -142,27 +142,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// We cannot derive `Default` because `T` does not necessarily implement `Default`.
|
||||
#[cfg(test)]
|
||||
impl<T: Config> Default for RentParams<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
total_balance: Default::default(),
|
||||
free_balance: Default::default(),
|
||||
subsistence_threshold: Default::default(),
|
||||
deposit_per_contract: Default::default(),
|
||||
deposit_per_storage_byte: Default::default(),
|
||||
deposit_per_storage_item: Default::default(),
|
||||
rent_allowance: Default::default(),
|
||||
rent_fraction: Default::default(),
|
||||
storage_size: Default::default(),
|
||||
code_size: Default::default(),
|
||||
code_refcount: Default::default(),
|
||||
_reserved: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An interface that provides access to the external environment in which the
|
||||
/// smart-contract is executed.
|
||||
///
|
||||
@@ -313,6 +292,9 @@ pub trait Ext: sealing::Sealed {
|
||||
/// Information needed for rent calculations.
|
||||
fn rent_params(&self) -> &RentParams<Self::T>;
|
||||
|
||||
/// Information about the required deposit and resulting rent.
|
||||
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T>;
|
||||
|
||||
/// Get a mutable reference to the nested gas meter.
|
||||
fn gas_meter(&mut self) -> &mut GasMeter<Self::T>;
|
||||
|
||||
@@ -909,11 +891,11 @@ where
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let Some(message) = &self.debug_message {
|
||||
if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) {
|
||||
log::debug!(
|
||||
target: "runtime::contracts",
|
||||
"Debug Message: {}",
|
||||
core::str::from_utf8(message).unwrap_or("<Invalid UTF8>"),
|
||||
"Execution finished with debug buffer: {}",
|
||||
core::str::from_utf8(msg).unwrap_or("<Invalid UTF8>"),
|
||||
);
|
||||
}
|
||||
// Write back to the root gas meter.
|
||||
@@ -1240,6 +1222,20 @@ where
|
||||
&self.top_frame().rent_params
|
||||
}
|
||||
|
||||
fn rent_status(&mut self, at_refcount: u32) -> RentStatus<Self::T> {
|
||||
let frame = self.top_frame_mut();
|
||||
let balance = T::Currency::free_balance(&frame.account_id);
|
||||
let code_size = frame.rent_params.code_size;
|
||||
let refcount = frame.rent_params.code_refcount;
|
||||
<Rent<T, E>>::rent_status(
|
||||
&balance,
|
||||
&frame.contract_info(),
|
||||
code_size,
|
||||
refcount,
|
||||
at_refcount,
|
||||
)
|
||||
}
|
||||
|
||||
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
|
||||
&mut self.top_frame_mut().nested_meter
|
||||
}
|
||||
@@ -2194,6 +2190,48 @@ mod tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rent_status_works() {
|
||||
let code_hash = MockLoader::insert(Call, |ctx, _| {
|
||||
assert_eq!(ctx.ext.rent_status(0), RentStatus {
|
||||
max_deposit: 80000,
|
||||
current_deposit: 80000,
|
||||
custom_refcount_deposit: None,
|
||||
max_rent: 32,
|
||||
current_rent: 32,
|
||||
custom_refcount_rent: None,
|
||||
_reserved: None,
|
||||
});
|
||||
assert_eq!(ctx.ext.rent_status(1), RentStatus {
|
||||
max_deposit: 80000,
|
||||
current_deposit: 80000,
|
||||
custom_refcount_deposit: Some(80000),
|
||||
max_rent: 32,
|
||||
current_rent: 32,
|
||||
custom_refcount_rent: Some(32),
|
||||
_reserved: None,
|
||||
});
|
||||
exec_success()
|
||||
});
|
||||
|
||||
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![],
|
||||
None,
|
||||
).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn in_memory_changes_not_discarded() {
|
||||
// Call stack: BOB -> CHARLIE (trap) -> BOB' (success)
|
||||
|
||||
@@ -23,6 +23,7 @@ use frame_support::{
|
||||
DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo, DispatchError,
|
||||
},
|
||||
weights::Weight,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
|
||||
@@ -73,6 +74,7 @@ pub struct ErasedToken {
|
||||
pub token: Box<dyn Any>,
|
||||
}
|
||||
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GasMeter<T: Config> {
|
||||
gas_limit: Weight,
|
||||
/// Amount of gas left from initial gas limit. Can reach zero.
|
||||
@@ -82,18 +84,6 @@ pub struct GasMeter<T: Config> {
|
||||
tokens: Vec<ErasedToken>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for GasMeter<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gas_limit: Default::default(),
|
||||
gas_left: Default::default(),
|
||||
_phantom: Default::default(),
|
||||
#[cfg(test)]
|
||||
tokens: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> GasMeter<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<<T as frame_system::Config>::Hash> + AsRef<[u8]>
|
||||
|
||||
@@ -28,6 +28,7 @@ use sp_core::crypto::UncheckedFrom;
|
||||
use frame_support::{
|
||||
storage::child,
|
||||
traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons},
|
||||
DefaultNoBound,
|
||||
};
|
||||
use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
|
||||
use sp_runtime::{
|
||||
@@ -35,54 +36,32 @@ use sp_runtime::{
|
||||
traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
|
||||
};
|
||||
|
||||
/// The amount to charge.
|
||||
/// Information about the required deposit and resulting rent.
|
||||
///
|
||||
/// This amount respects the contract's rent allowance and the subsistence deposit.
|
||||
/// Because of that, charging the amount cannot remove the contract.
|
||||
struct OutstandingAmount<T: Config> {
|
||||
amount: BalanceOf<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> OutstandingAmount<T> {
|
||||
/// Create the new outstanding amount.
|
||||
///
|
||||
/// The amount should be always withdrawable and it should not kill the account.
|
||||
fn new(amount: BalanceOf<T>) -> Self {
|
||||
Self { amount }
|
||||
}
|
||||
|
||||
/// Returns the amount this instance wraps.
|
||||
fn peek(&self) -> BalanceOf<T> {
|
||||
self.amount
|
||||
}
|
||||
|
||||
/// Withdraws the outstanding amount from the given account.
|
||||
fn withdraw(self, account: &T::AccountId) {
|
||||
if let Ok(imbalance) = T::Currency::withdraw(
|
||||
account,
|
||||
self.amount,
|
||||
WithdrawReasons::FEE,
|
||||
ExistenceRequirement::KeepAlive,
|
||||
) {
|
||||
// This should never fail. However, let's err on the safe side.
|
||||
T::RentPayment::on_unbalanced(imbalance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Verdict<T: Config> {
|
||||
/// The contract is exempted from paying rent.
|
||||
///
|
||||
/// For example, it already paid its rent in the current block, or it has enough deposit for not
|
||||
/// paying rent at all.
|
||||
Exempt,
|
||||
/// The contract cannot afford payment within its rent budget so it gets evicted. However,
|
||||
/// because its balance is greater than the subsistence threshold it leaves a tombstone.
|
||||
Evict {
|
||||
amount: Option<OutstandingAmount<T>>,
|
||||
},
|
||||
/// Everything is OK, we just only take some charge.
|
||||
Charge { amount: OutstandingAmount<T> },
|
||||
/// The easiest way to guarantee that a contract stays alive is to assert that
|
||||
/// `max_rent == 0` at the **end** of a contract's execution.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The `current_*` fields do **not** consider changes to the code's refcount made during
|
||||
/// the currently running call.
|
||||
#[derive(codec::Encode, DefaultNoBound)]
|
||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||
pub struct RentStatus<T: Config> {
|
||||
/// Required deposit assuming that this contract is the only user of its code.
|
||||
pub max_deposit: BalanceOf<T>,
|
||||
/// Required deposit assuming the code's current refcount.
|
||||
pub current_deposit: BalanceOf<T>,
|
||||
/// Required deposit assuming the specified refcount (None if 0 is supplied).
|
||||
pub custom_refcount_deposit: Option<BalanceOf<T>>,
|
||||
/// Rent that is paid assuming that the contract is the only user of its code.
|
||||
pub max_rent: BalanceOf<T>,
|
||||
/// Rent that is paid given the code's current refcount.
|
||||
pub current_rent: BalanceOf<T>,
|
||||
/// Rent that is paid assuming the specified refcount (None is 0 is supplied).
|
||||
pub custom_refcount_rent: Option<BalanceOf<T>>,
|
||||
/// Reserved for backwards compatible changes to this data structure.
|
||||
pub _reserved: Option<()>,
|
||||
}
|
||||
|
||||
pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);
|
||||
@@ -93,24 +72,304 @@ where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
|
||||
E: Executable<T>,
|
||||
{
|
||||
/// Returns a fee charged per block from the contract.
|
||||
/// Make account paying the rent for the current block number
|
||||
///
|
||||
/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
|
||||
/// then the fee can drop to zero.
|
||||
fn compute_fee_per_block(
|
||||
/// This functions does **not** evict the contract. It returns `None` in case the
|
||||
/// contract is in need of eviction. [`try_eviction`] must
|
||||
/// be called to perform the eviction.
|
||||
pub fn charge(
|
||||
account: &T::AccountId,
|
||||
contract: AliveContractInfo<T>,
|
||||
code_size: u32,
|
||||
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
account,
|
||||
current_block_number,
|
||||
Zero::zero(),
|
||||
&contract,
|
||||
code_size,
|
||||
);
|
||||
Self::enact_verdict(account, contract, current_block_number, verdict, None)
|
||||
}
|
||||
|
||||
/// Process a report that a contract under the given address should be evicted.
|
||||
///
|
||||
/// Enact the eviction right away if the contract should be evicted and return the amount
|
||||
/// of rent that the contract payed over its lifetime.
|
||||
/// Otherwise, **do nothing** and return None.
|
||||
///
|
||||
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
|
||||
/// of current block. E.g. if the contract is going to be evicted at the current block,
|
||||
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers
|
||||
/// relative to others.
|
||||
///
|
||||
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
|
||||
/// storage.
|
||||
pub fn try_eviction(
|
||||
account: &T::AccountId,
|
||||
handicap: T::BlockNumber,
|
||||
) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {
|
||||
let contract = <ContractInfoOf<T>>::get(account);
|
||||
let contract = match contract {
|
||||
None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),
|
||||
Some(ContractInfo::Alive(contract)) => contract,
|
||||
};
|
||||
let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
|
||||
let code_len = module.code_len();
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
account,
|
||||
current_block_number,
|
||||
handicap,
|
||||
&contract,
|
||||
module.occupied_storage(),
|
||||
);
|
||||
|
||||
// Enact the verdict only if the contract gets removed.
|
||||
match verdict {
|
||||
Verdict::Evict { ref amount } => {
|
||||
// The outstanding `amount` is withdrawn inside `enact_verdict`.
|
||||
let rent_payed = amount
|
||||
.as_ref()
|
||||
.map(|a| a.peek())
|
||||
.unwrap_or_else(|| <BalanceOf<T>>::zero())
|
||||
.saturating_add(contract.rent_payed);
|
||||
Self::enact_verdict(
|
||||
account, contract, current_block_number, verdict, Some(module),
|
||||
)?;
|
||||
Ok((Some(rent_payed), code_len))
|
||||
}
|
||||
_ => Ok((None, code_len)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the projected time a given contract will be able to sustain paying its rent. The
|
||||
/// returned projection is relevant for the current block, i.e. it is as if the contract was
|
||||
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
|
||||
/// evicted before or as a result of the rent collection.
|
||||
///
|
||||
/// The returned value is only an estimation. It doesn't take into account any top ups, changing the
|
||||
/// rent allowance, or any problems coming from withdrawing the dues.
|
||||
///
|
||||
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
|
||||
/// compute the projection. This function is only used for implementation of an RPC method through
|
||||
/// `RuntimeApi` meaning that the changes will be discarded anyway.
|
||||
pub fn compute_projection(
|
||||
account: &T::AccountId,
|
||||
) -> RentProjectionResult<T::BlockNumber> {
|
||||
use ContractAccessError::IsTombstone;
|
||||
|
||||
let contract_info = <ContractInfoOf<T>>::get(account);
|
||||
let alive_contract_info = match contract_info {
|
||||
None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
|
||||
Some(ContractInfo::Alive(contract)) => contract,
|
||||
};
|
||||
let module = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)
|
||||
.map_err(|_| IsTombstone)?;
|
||||
let code_size = module.occupied_storage();
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
account,
|
||||
current_block_number,
|
||||
Zero::zero(),
|
||||
&alive_contract_info,
|
||||
code_size,
|
||||
);
|
||||
|
||||
// We skip the eviction in case one is in order.
|
||||
// Evictions should only be performed by [`try_eviction`].
|
||||
let new_contract_info = Self::enact_verdict(
|
||||
account, alive_contract_info, current_block_number, verdict, None,
|
||||
);
|
||||
|
||||
// Check what happened after enaction of the verdict.
|
||||
let alive_contract_info = new_contract_info.map_err(|_| IsTombstone)?.ok_or_else(|| IsTombstone)?;
|
||||
|
||||
// Compute how much would the fee per block be with the *updated* balance.
|
||||
let total_balance = T::Currency::total_balance(account);
|
||||
let free_balance = T::Currency::free_balance(account);
|
||||
let fee_per_block = Self::fee_per_block(
|
||||
&free_balance, &alive_contract_info, code_size,
|
||||
);
|
||||
if fee_per_block.is_zero() {
|
||||
return Ok(RentProjection::NoEviction);
|
||||
}
|
||||
|
||||
// Then compute how much the contract will sustain under these circumstances.
|
||||
let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info).expect(
|
||||
"the contract exists and in the alive state;
|
||||
the updated balance must be greater than subsistence deposit;
|
||||
this function doesn't return `None`;
|
||||
qed
|
||||
",
|
||||
);
|
||||
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
|
||||
Some(blocks_left) => blocks_left,
|
||||
None => {
|
||||
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
|
||||
// there is an overflow. This cannot happen with integers though. Return
|
||||
// `NoEviction` here just in case.
|
||||
return Ok(RentProjection::NoEviction);
|
||||
}
|
||||
};
|
||||
|
||||
let blocks_left = blocks_left.saturated_into::<u32>().into();
|
||||
Ok(RentProjection::EvictionAt(
|
||||
current_block_number + blocks_left,
|
||||
))
|
||||
}
|
||||
|
||||
/// Restores the destination account using the origin as prototype.
|
||||
///
|
||||
/// The restoration will be performed iff:
|
||||
/// - the supplied code_hash does still exist on-chain
|
||||
/// - origin exists and is alive,
|
||||
/// - the origin's storage is not written in the current block
|
||||
/// - the restored account has tombstone
|
||||
/// - the tombstone matches the hash of the origin storage root, and code hash.
|
||||
///
|
||||
/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
|
||||
/// the restored account. The restored account will inherit the last write block and its last
|
||||
/// deduct block will be set to the current block.
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
|
||||
pub fn restore_to(
|
||||
origin: &T::AccountId,
|
||||
mut origin_contract: AliveContractInfo<T>,
|
||||
dest: T::AccountId,
|
||||
code_hash: CodeHash<T>,
|
||||
rent_allowance: BalanceOf<T>,
|
||||
delta: Vec<crate::exec::StorageKey>,
|
||||
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
|
||||
let child_trie_info = origin_contract.child_trie_info();
|
||||
|
||||
let current_block = <frame_system::Pallet<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err((Error::<T>::InvalidContractOrigin.into(), 0, 0));
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
|
||||
.and_then(|c| c.get_tombstone())
|
||||
.ok_or((Error::<T>::InvalidDestinationContract.into(), 0, 0))?;
|
||||
|
||||
let last_write = if !delta.is_empty() {
|
||||
Some(current_block)
|
||||
} else {
|
||||
origin_contract.last_write
|
||||
};
|
||||
|
||||
// Fails if the code hash does not exist on chain
|
||||
let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?;
|
||||
|
||||
// We are allowed to eagerly modify storage even though the function can
|
||||
// fail later due to tombstones not matching. This is because the restoration
|
||||
// is always called from a contract and therefore in a storage transaction.
|
||||
// The failure of this function will lead to this transaction's rollback.
|
||||
let bytes_taken: u32 = delta.iter()
|
||||
.filter_map(|key| {
|
||||
let key = blake2_256(key);
|
||||
child::get_raw(&child_trie_info, &key).map(|value| {
|
||||
child::kill(&child_trie_info, &key);
|
||||
value.len() as u32
|
||||
})
|
||||
})
|
||||
.sum();
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
// This operation is cheap enough because last_write (delta not included)
|
||||
// is not this block as it has been checked earlier.
|
||||
&child::root(&child_trie_info)[..],
|
||||
code_hash,
|
||||
);
|
||||
|
||||
if tombstone != dest_tombstone {
|
||||
return Err((Error::<T>::InvalidTombstone.into(), caller_code_len, 0));
|
||||
}
|
||||
|
||||
origin_contract.storage_size -= bytes_taken;
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
let tombstone_code_len = E::remove_user(origin_contract.code_hash);
|
||||
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
rent_payed: <BalanceOf<T>>::zero(),
|
||||
deduct_block: current_block,
|
||||
last_write,
|
||||
.. origin_contract
|
||||
}));
|
||||
|
||||
let origin_free_balance = T::Currency::free_balance(&origin);
|
||||
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
|
||||
T::Currency::deposit_creating(&dest, origin_free_balance);
|
||||
|
||||
Ok((caller_code_len, tombstone_code_len))
|
||||
}
|
||||
|
||||
/// Create a new `RentStatus` struct for pass through to a requesting contract.
|
||||
pub fn rent_status(
|
||||
free_balance: &BalanceOf<T>,
|
||||
contract: &AliveContractInfo<T>,
|
||||
aggregated_code_size: u32,
|
||||
current_refcount: u32,
|
||||
at_refcount: u32,
|
||||
) -> RentStatus<T> {
|
||||
let calc_share = |refcount: u32| {
|
||||
aggregated_code_size.checked_div(refcount).unwrap_or(0)
|
||||
};
|
||||
let current_share = calc_share(current_refcount);
|
||||
let custom_share = calc_share(at_refcount);
|
||||
RentStatus {
|
||||
max_deposit: Self::required_deposit(contract, aggregated_code_size),
|
||||
current_deposit: Self::required_deposit(contract, current_share),
|
||||
custom_refcount_deposit:
|
||||
if at_refcount > 0 {
|
||||
Some(Self::required_deposit(contract, custom_share))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
max_rent: Self::fee_per_block(free_balance, contract, aggregated_code_size),
|
||||
current_rent: Self::fee_per_block(free_balance, contract, current_share),
|
||||
custom_refcount_rent:
|
||||
if at_refcount > 0 {
|
||||
Some(Self::fee_per_block(free_balance, contract, custom_share))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
_reserved: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns how much deposit is required to not pay rent.
|
||||
fn required_deposit(
|
||||
contract: &AliveContractInfo<T>,
|
||||
code_size_share: u32,
|
||||
) -> BalanceOf<T> {
|
||||
let uncovered_by_balance = T::DepositPerStorageByte::get()
|
||||
T::DepositPerStorageByte::get()
|
||||
.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
|
||||
.saturating_add(
|
||||
T::DepositPerStorageItem::get()
|
||||
.saturating_mul(contract.pair_count.into())
|
||||
)
|
||||
.saturating_add(T::DepositPerContract::get())
|
||||
}
|
||||
|
||||
/// Returns a fee charged per block from the contract.
|
||||
///
|
||||
/// This function accounts for the storage rent deposit. I.e. if the contract
|
||||
/// possesses enough funds then the fee can drop to zero.
|
||||
fn fee_per_block(
|
||||
free_balance: &BalanceOf<T>,
|
||||
contract: &AliveContractInfo<T>,
|
||||
code_size_share: u32,
|
||||
) -> BalanceOf<T> {
|
||||
let missing_deposit = Self::required_deposit(contract, code_size_share)
|
||||
.saturating_sub(*free_balance);
|
||||
T::RentFraction::get().mul_ceil(uncovered_by_balance)
|
||||
T::RentFraction::get().mul_ceil(missing_deposit)
|
||||
}
|
||||
|
||||
/// Returns amount of funds available to consume by rent mechanism.
|
||||
@@ -166,7 +425,7 @@ where
|
||||
let free_balance = T::Currency::free_balance(account);
|
||||
|
||||
// An amount of funds to charge per block for storage taken up by the contract.
|
||||
let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);
|
||||
let fee_per_block = Self::fee_per_block(&free_balance, contract, code_size);
|
||||
if fee_per_block.is_zero() {
|
||||
// The rent deposit offset reduced the fee to 0. This means that the contract
|
||||
// gets the rent for free.
|
||||
@@ -295,241 +554,55 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Make account paying the rent for the current block number
|
||||
|
||||
}
|
||||
|
||||
/// The amount to charge.
|
||||
///
|
||||
/// This amount respects the contract's rent allowance and the subsistence deposit.
|
||||
/// Because of that, charging the amount cannot remove the contract.
|
||||
struct OutstandingAmount<T: Config> {
|
||||
amount: BalanceOf<T>,
|
||||
}
|
||||
|
||||
impl<T: Config> OutstandingAmount<T> {
|
||||
/// Create the new outstanding amount.
|
||||
///
|
||||
/// This functions does **not** evict the contract. It returns `None` in case the
|
||||
/// contract is in need of eviction. [`try_eviction`] must
|
||||
/// be called to perform the eviction.
|
||||
pub fn charge(
|
||||
account: &T::AccountId,
|
||||
contract: AliveContractInfo<T>,
|
||||
code_size: u32,
|
||||
) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
account,
|
||||
current_block_number,
|
||||
Zero::zero(),
|
||||
&contract,
|
||||
code_size,
|
||||
);
|
||||
Self::enact_verdict(account, contract, current_block_number, verdict, None)
|
||||
/// The amount should be always withdrawable and it should not kill the account.
|
||||
fn new(amount: BalanceOf<T>) -> Self {
|
||||
Self { amount }
|
||||
}
|
||||
|
||||
/// Process a report that a contract under the given address should be evicted.
|
||||
///
|
||||
/// Enact the eviction right away if the contract should be evicted and return the amount
|
||||
/// of rent that the contract payed over its lifetime.
|
||||
/// Otherwise, **do nothing** and return None.
|
||||
///
|
||||
/// The `handicap` parameter gives a way to check the rent to a moment in the past instead
|
||||
/// of current block. E.g. if the contract is going to be evicted at the current block,
|
||||
/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers
|
||||
/// relative to others.
|
||||
///
|
||||
/// NOTE this function performs eviction eagerly. All changes are read and written directly to
|
||||
/// storage.
|
||||
pub fn try_eviction(
|
||||
account: &T::AccountId,
|
||||
handicap: T::BlockNumber,
|
||||
) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {
|
||||
let contract = <ContractInfoOf<T>>::get(account);
|
||||
let contract = match contract {
|
||||
None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),
|
||||
Some(ContractInfo::Alive(contract)) => contract,
|
||||
};
|
||||
let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
|
||||
let code_len = module.code_len();
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
account,
|
||||
current_block_number,
|
||||
handicap,
|
||||
&contract,
|
||||
module.occupied_storage(),
|
||||
);
|
||||
|
||||
// Enact the verdict only if the contract gets removed.
|
||||
match verdict {
|
||||
Verdict::Evict { ref amount } => {
|
||||
// The outstanding `amount` is withdrawn inside `enact_verdict`.
|
||||
let rent_payed = amount
|
||||
.as_ref()
|
||||
.map(|a| a.peek())
|
||||
.unwrap_or_else(|| <BalanceOf<T>>::zero())
|
||||
.saturating_add(contract.rent_payed);
|
||||
Self::enact_verdict(
|
||||
account, contract, current_block_number, verdict, Some(module),
|
||||
)?;
|
||||
Ok((Some(rent_payed), code_len))
|
||||
}
|
||||
_ => Ok((None, code_len)),
|
||||
}
|
||||
/// Returns the amount this instance wraps.
|
||||
fn peek(&self) -> BalanceOf<T> {
|
||||
self.amount
|
||||
}
|
||||
|
||||
/// Returns the projected time a given contract will be able to sustain paying its rent. The
|
||||
/// returned projection is relevant for the current block, i.e. it is as if the contract was
|
||||
/// accessed at the beginning of the current block. Returns `None` in case if the contract was
|
||||
/// evicted before or as a result of the rent collection.
|
||||
///
|
||||
/// The returned value is only an estimation. It doesn't take into account any top ups, changing the
|
||||
/// rent allowance, or any problems coming from withdrawing the dues.
|
||||
///
|
||||
/// NOTE that this is not a side-effect free function! It will actually collect rent and then
|
||||
/// compute the projection. This function is only used for implementation of an RPC method through
|
||||
/// `RuntimeApi` meaning that the changes will be discarded anyway.
|
||||
pub fn compute_projection(
|
||||
account: &T::AccountId,
|
||||
) -> RentProjectionResult<T::BlockNumber> {
|
||||
use ContractAccessError::IsTombstone;
|
||||
|
||||
let contract_info = <ContractInfoOf<T>>::get(account);
|
||||
let alive_contract_info = match contract_info {
|
||||
None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
|
||||
Some(ContractInfo::Alive(contract)) => contract,
|
||||
};
|
||||
let module = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)
|
||||
.map_err(|_| IsTombstone)?;
|
||||
let code_size = module.occupied_storage();
|
||||
let current_block_number = <frame_system::Pallet<T>>::block_number();
|
||||
let verdict = Self::consider_case(
|
||||
/// Withdraws the outstanding amount from the given account.
|
||||
fn withdraw(self, account: &T::AccountId) {
|
||||
if let Ok(imbalance) = T::Currency::withdraw(
|
||||
account,
|
||||
current_block_number,
|
||||
Zero::zero(),
|
||||
&alive_contract_info,
|
||||
code_size,
|
||||
);
|
||||
|
||||
// We skip the eviction in case one is in order.
|
||||
// Evictions should only be performed by [`try_eviction`].
|
||||
let new_contract_info = Self::enact_verdict(
|
||||
account, alive_contract_info, current_block_number, verdict, None,
|
||||
);
|
||||
|
||||
// Check what happened after enaction of the verdict.
|
||||
let alive_contract_info = new_contract_info.map_err(|_| IsTombstone)?.ok_or_else(|| IsTombstone)?;
|
||||
|
||||
// Compute how much would the fee per block be with the *updated* balance.
|
||||
let total_balance = T::Currency::total_balance(account);
|
||||
let free_balance = T::Currency::free_balance(account);
|
||||
let fee_per_block = Self::compute_fee_per_block(
|
||||
&free_balance, &alive_contract_info, code_size,
|
||||
);
|
||||
if fee_per_block.is_zero() {
|
||||
return Ok(RentProjection::NoEviction);
|
||||
self.amount,
|
||||
WithdrawReasons::FEE,
|
||||
ExistenceRequirement::KeepAlive,
|
||||
) {
|
||||
// This should never fail. However, let's err on the safe side.
|
||||
T::RentPayment::on_unbalanced(imbalance);
|
||||
}
|
||||
|
||||
// Then compute how much the contract will sustain under these circumstances.
|
||||
let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info).expect(
|
||||
"the contract exists and in the alive state;
|
||||
the updated balance must be greater than subsistence deposit;
|
||||
this function doesn't return `None`;
|
||||
qed
|
||||
",
|
||||
);
|
||||
let blocks_left = match rent_budget.checked_div(&fee_per_block) {
|
||||
Some(blocks_left) => blocks_left,
|
||||
None => {
|
||||
// `fee_per_block` is not zero here, so `checked_div` can return `None` if
|
||||
// there is an overflow. This cannot happen with integers though. Return
|
||||
// `NoEviction` here just in case.
|
||||
return Ok(RentProjection::NoEviction);
|
||||
}
|
||||
};
|
||||
|
||||
let blocks_left = blocks_left.saturated_into::<u32>().into();
|
||||
Ok(RentProjection::EvictionAt(
|
||||
current_block_number + blocks_left,
|
||||
))
|
||||
}
|
||||
|
||||
/// Restores the destination account using the origin as prototype.
|
||||
///
|
||||
/// The restoration will be performed iff:
|
||||
/// - the supplied code_hash does still exist on-chain
|
||||
/// - origin exists and is alive,
|
||||
/// - the origin's storage is not written in the current block
|
||||
/// - the restored account has tombstone
|
||||
/// - the tombstone matches the hash of the origin storage root, and code hash.
|
||||
///
|
||||
/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
|
||||
/// the restored account. The restored account will inherit the last write block and its last
|
||||
/// deduct block will be set to the current block.
|
||||
///
|
||||
/// # Return Value
|
||||
///
|
||||
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
|
||||
pub fn restore_to(
|
||||
origin: &T::AccountId,
|
||||
mut origin_contract: AliveContractInfo<T>,
|
||||
dest: T::AccountId,
|
||||
code_hash: CodeHash<T>,
|
||||
rent_allowance: BalanceOf<T>,
|
||||
delta: Vec<crate::exec::StorageKey>,
|
||||
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
|
||||
let child_trie_info = origin_contract.child_trie_info();
|
||||
|
||||
let current_block = <frame_system::Pallet<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err((Error::<T>::InvalidContractOrigin.into(), 0, 0));
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
|
||||
.and_then(|c| c.get_tombstone())
|
||||
.ok_or((Error::<T>::InvalidDestinationContract.into(), 0, 0))?;
|
||||
|
||||
let last_write = if !delta.is_empty() {
|
||||
Some(current_block)
|
||||
} else {
|
||||
origin_contract.last_write
|
||||
};
|
||||
|
||||
// Fails if the code hash does not exist on chain
|
||||
let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?;
|
||||
|
||||
// We are allowed to eagerly modify storage even though the function can
|
||||
// fail later due to tombstones not matching. This is because the restoration
|
||||
// is always called from a contract and therefore in a storage transaction.
|
||||
// The failure of this function will lead to this transaction's rollback.
|
||||
let bytes_taken: u32 = delta.iter()
|
||||
.filter_map(|key| {
|
||||
let key = blake2_256(key);
|
||||
child::get_raw(&child_trie_info, &key).map(|value| {
|
||||
child::kill(&child_trie_info, &key);
|
||||
value.len() as u32
|
||||
})
|
||||
})
|
||||
.sum();
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
// This operation is cheap enough because last_write (delta not included)
|
||||
// is not this block as it has been checked earlier.
|
||||
&child::root(&child_trie_info)[..],
|
||||
code_hash,
|
||||
);
|
||||
|
||||
if tombstone != dest_tombstone {
|
||||
return Err((Error::<T>::InvalidTombstone.into(), caller_code_len, 0));
|
||||
}
|
||||
|
||||
origin_contract.storage_size -= bytes_taken;
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
let tombstone_code_len = E::remove_user(origin_contract.code_hash);
|
||||
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
rent_payed: <BalanceOf<T>>::zero(),
|
||||
deduct_block: current_block,
|
||||
last_write,
|
||||
.. origin_contract
|
||||
}));
|
||||
|
||||
let origin_free_balance = T::Currency::free_balance(&origin);
|
||||
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
|
||||
T::Currency::deposit_creating(&dest, origin_free_balance);
|
||||
|
||||
Ok((caller_code_len, tombstone_code_len))
|
||||
}
|
||||
}
|
||||
|
||||
enum Verdict<T: Config> {
|
||||
/// The contract is exempted from paying rent.
|
||||
///
|
||||
/// For example, it already paid its rent in the current block, or it has enough deposit for not
|
||||
/// paying rent at all.
|
||||
Exempt,
|
||||
/// The contract cannot afford payment within its rent budget so it gets evicted. However,
|
||||
/// because its balance is greater than the subsistence threshold it leaves a tombstone.
|
||||
Evict {
|
||||
amount: Option<OutstandingAmount<T>>,
|
||||
},
|
||||
/// Everything is OK, we just only take some charge.
|
||||
Charge { amount: OutstandingAmount<T> },
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::{Config, weights::WeightInfo};
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Serialize, Deserialize};
|
||||
use pallet_contracts_proc_macro::{ScheduleDebug, WeightDebug};
|
||||
use frame_support::weights::Weight;
|
||||
use frame_support::{DefaultNoBound, weights::Weight};
|
||||
use sp_std::{marker::PhantomData, vec::Vec};
|
||||
use codec::{Encode, Decode};
|
||||
use parity_wasm::elements;
|
||||
@@ -46,7 +46,7 @@ pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 1_000;
|
||||
/// 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)]
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, DefaultNoBound)]
|
||||
pub struct Schedule<T: Config> {
|
||||
/// Describes the upper limits on various metrics.
|
||||
pub(crate) limits: Limits,
|
||||
@@ -388,9 +388,6 @@ pub struct HostFnWeights<T: Config> {
|
||||
/// Weight per byte hashed by `seal_hash_blake2_128`.
|
||||
pub hash_blake2_128_per_byte: Weight,
|
||||
|
||||
/// Weight of calling `seal_rent_params`.
|
||||
pub rent_params: Weight,
|
||||
|
||||
/// The type parameter is used in the default implementation.
|
||||
#[codec(skip)]
|
||||
pub _phantom: PhantomData<T>
|
||||
@@ -473,16 +470,6 @@ macro_rules! cost_byte_batched {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Default for Schedule<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
limits: Default::default(),
|
||||
instruction_weights: Default::default(),
|
||||
host_fn_weights: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Limits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
@@ -619,7 +606,6 @@ impl<T: Config> Default for HostFnWeights<T> {
|
||||
hash_blake2_256_per_byte: cost_byte_batched!(seal_hash_blake2_256_per_kb),
|
||||
hash_blake2_128: cost_batched!(seal_hash_blake2_128),
|
||||
hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb),
|
||||
rent_params: cost_batched!(seal_rent_params),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2835,6 +2835,7 @@ fn reinstrument_does_charge() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn debug_message_works() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("debug_message_works").unwrap();
|
||||
|
||||
@@ -2866,6 +2867,7 @@ fn debug_message_works() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn debug_message_logging_disabled() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("debug_message_logging_disabled").unwrap();
|
||||
|
||||
@@ -2905,6 +2907,7 @@ fn debug_message_logging_disabled() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn debug_message_invalid_utf8() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("debug_message_invalid_utf8").unwrap();
|
||||
|
||||
|
||||
@@ -50,7 +50,12 @@ macro_rules! gen_signature_dispatch {
|
||||
$name:ident
|
||||
( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)*
|
||||
) => {
|
||||
if stringify!($module).as_bytes() == $needle_module && stringify!($name).as_bytes() == $needle_name {
|
||||
let module = stringify!($module).as_bytes();
|
||||
#[cfg(not(feature = "unstable-interface"))]
|
||||
if module == b"__unstable__" {
|
||||
return false;
|
||||
}
|
||||
if module == $needle_module && stringify!($name).as_bytes() == $needle_name {
|
||||
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
|
||||
if $needle_sig == &signature {
|
||||
return true;
|
||||
@@ -127,8 +132,8 @@ macro_rules! unmarshall_then_body_then_marshall {
|
||||
}
|
||||
|
||||
macro_rules! define_func {
|
||||
( < E: $seal_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
|
||||
fn $name< E: $seal_ty >(
|
||||
( $trait:tt $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
|
||||
fn $name< E: $trait >(
|
||||
$ctx: &mut $crate::wasm::Runtime<E>,
|
||||
args: &[sp_sandbox::Value],
|
||||
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>
|
||||
@@ -149,24 +154,52 @@ macro_rules! define_func {
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! register_func {
|
||||
( $reg_cb:ident, < E: $seal_ty:tt > ; ) => {};
|
||||
|
||||
( $reg_cb:ident, < E: $seal_ty:tt > ;
|
||||
macro_rules! register_body {
|
||||
( $reg_cb:ident, $trait:tt;
|
||||
$module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt $($rest:tt)*
|
||||
$( -> $returns:ty )* => $body:tt
|
||||
) => {
|
||||
$reg_cb(
|
||||
stringify!($module).as_bytes(),
|
||||
stringify!($name).as_bytes(),
|
||||
{
|
||||
define_func!(
|
||||
< E: $seal_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
|
||||
$trait $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
|
||||
);
|
||||
$name::<E>
|
||||
}
|
||||
);
|
||||
register_func!( $reg_cb, < E: $seal_ty > ; $($rest)* );
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! register_func {
|
||||
( $reg_cb:ident, $trait:tt; ) => {};
|
||||
|
||||
( $reg_cb:ident, $trait:tt;
|
||||
__unstable__ $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt $($rest:tt)*
|
||||
) => {
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
register_body!(
|
||||
$reg_cb, $trait;
|
||||
__unstable__ $name
|
||||
( $ctx $( , $names : $params )* )
|
||||
$( -> $returns )* => $body
|
||||
);
|
||||
register_func!( $reg_cb, $trait; $($rest)* );
|
||||
};
|
||||
|
||||
( $reg_cb:ident, $trait:tt;
|
||||
$module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt $($rest:tt)*
|
||||
) => {
|
||||
register_body!(
|
||||
$reg_cb, $trait;
|
||||
$module $name
|
||||
( $ctx $( , $names : $params )* )
|
||||
$( -> $returns )* => $body
|
||||
);
|
||||
register_func!( $reg_cb, $trait; $($rest)* );
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,7 +211,7 @@ macro_rules! register_func {
|
||||
/// It's up to the user of this macro to check signatures of wasm code to be executed
|
||||
/// and reject the code if any imported function has a mismatched signature.
|
||||
macro_rules! define_env {
|
||||
( $init_name:ident , < E: $seal_ty:tt > ,
|
||||
( $init_name:ident , < E: $trait:tt > ,
|
||||
$( [$module:ident] $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
|
||||
$( -> $returns:ty )* => $body:tt , )*
|
||||
) => {
|
||||
@@ -204,7 +237,7 @@ macro_rules! define_env {
|
||||
fn impls<F: FnMut(&[u8], &[u8], $crate::wasm::env_def::HostFunc<E>)>(f: &mut F) {
|
||||
register_func!(
|
||||
f,
|
||||
< E: $seal_ty > ;
|
||||
$trait;
|
||||
$( $module $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )*
|
||||
);
|
||||
}
|
||||
@@ -285,7 +318,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn macro_define_func() {
|
||||
define_func!( <E: Ext> seal_gas (_ctx, amount: u32) => {
|
||||
define_func!( Ext seal_gas (_ctx, amount: u32) => {
|
||||
let amount = Weight::from(amount);
|
||||
if !amount.is_zero() {
|
||||
Ok(())
|
||||
|
||||
@@ -247,6 +247,7 @@ mod tests {
|
||||
RentParams, ExecError, ErrorOrigin,
|
||||
},
|
||||
gas::GasMeter,
|
||||
rent::RentStatus,
|
||||
tests::{Test, Call, ALICE, BOB},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
@@ -452,6 +453,9 @@ mod tests {
|
||||
fn rent_params(&self) -> &RentParams<Self::T> {
|
||||
&self.rent_params
|
||||
}
|
||||
fn rent_status(&mut self, _at_refcount: u32) -> RentStatus<Self::T> {
|
||||
Default::default()
|
||||
}
|
||||
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
|
||||
&mut self.gas_meter
|
||||
}
|
||||
@@ -1817,9 +1821,14 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_RENT_PARAMS: &str = r#"
|
||||
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn rent_params_work() {
|
||||
const CODE_RENT_PARAMS: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_rent_params" (func $seal_rent_params (param i32 i32)))
|
||||
(import "__unstable__" "seal_rent_params" (func $seal_rent_params (param i32 i32)))
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
@@ -1846,9 +1855,6 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn rent_params_work() {
|
||||
let output = execute(
|
||||
CODE_RENT_PARAMS,
|
||||
vec![],
|
||||
@@ -1858,9 +1864,56 @@ mod tests {
|
||||
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
|
||||
}
|
||||
|
||||
const CODE_DEBUG_MESSAGE: &str = r#"
|
||||
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn rent_status_works() {
|
||||
const CODE_RENT_STATUS: &str = r#"
|
||||
(module
|
||||
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "__unstable__" "seal_rent_status" (func $seal_rent_status (param i32 i32 i32)))
|
||||
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
;; [0, 4) buffer size = 128 bytes
|
||||
(data (i32.const 0) "\80")
|
||||
|
||||
;; [4; inf) buffer where the result is copied
|
||||
|
||||
(func (export "call")
|
||||
;; Load the rent params into memory
|
||||
(call $seal_rent_status
|
||||
(i32.const 1) ;; at_refcount
|
||||
(i32.const 4) ;; Pointer to the output buffer
|
||||
(i32.const 0) ;; Pointer to the size of the buffer
|
||||
)
|
||||
|
||||
;; Return the contents of the buffer
|
||||
(call $seal_return
|
||||
(i32.const 0) ;; return flags
|
||||
(i32.const 4) ;; buffer pointer
|
||||
(i32.load (i32.const 0)) ;; buffer size
|
||||
)
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
let output = execute(
|
||||
CODE_RENT_STATUS,
|
||||
vec![],
|
||||
MockExt::default(),
|
||||
).unwrap();
|
||||
let rent_status = Bytes(<RentStatus<Test>>::default().encode());
|
||||
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_status });
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn debug_message_works() {
|
||||
const CODE_DEBUG_MESSAGE: &str = r#"
|
||||
(module
|
||||
(import "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "Hello World!")
|
||||
@@ -1876,9 +1929,6 @@ mod tests {
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn debug_message_works() {
|
||||
let mut ext = MockExt::default();
|
||||
execute(
|
||||
CODE_DEBUG_MESSAGE,
|
||||
@@ -1889,39 +1939,39 @@ mod tests {
|
||||
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))
|
||||
#[test]
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
fn debug_message_invalid_utf8_fails() {
|
||||
const CODE_DEBUG_MESSAGE_FAIL: &str = r#"
|
||||
(module
|
||||
(import "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(data (i32.const 0) "\fc")
|
||||
(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 "call")
|
||||
(call $seal_debug_message
|
||||
(i32.const 0) ;; Pointer to the text buffer
|
||||
(i32.const 1) ;; The size of the buffer
|
||||
)
|
||||
|
||||
(func (export "deploy"))
|
||||
drop
|
||||
)
|
||||
"#;
|
||||
|
||||
#[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,
|
||||
})
|
||||
);
|
||||
}
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#;
|
||||
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,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,7 @@ pub enum ReturnCode {
|
||||
NotCallable = 8,
|
||||
/// The call to `seal_debug_message` had no effect because debug message
|
||||
/// recording was disabled.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
LoggingDisabled = 9,
|
||||
}
|
||||
|
||||
@@ -179,6 +180,7 @@ pub enum RuntimeCosts {
|
||||
/// 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`.
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
DebugMessage,
|
||||
/// Weight of calling `seal_set_rent_allowance`.
|
||||
SetRentAllowance,
|
||||
@@ -220,8 +222,6 @@ pub enum RuntimeCosts {
|
||||
ChainExtension(u64),
|
||||
/// Weight charged for copying data from the sandbox.
|
||||
CopyIn(u32),
|
||||
/// Weight of calling `seal_rent_params`.
|
||||
RentParams,
|
||||
}
|
||||
|
||||
impl RuntimeCosts {
|
||||
@@ -260,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())),
|
||||
#[cfg(feature = "unstable-interface")]
|
||||
DebugMessage => s.debug_message,
|
||||
SetRentAllowance => s.set_rent_allowance,
|
||||
SetStorage(len) => s.set_storage
|
||||
@@ -290,7 +291,6 @@ impl RuntimeCosts {
|
||||
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
||||
ChainExtension(amount) => amount,
|
||||
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
|
||||
RentParams => s.rent_params,
|
||||
};
|
||||
RuntimeToken {
|
||||
#[cfg(test)]
|
||||
@@ -1390,35 +1390,6 @@ define_env!(Env, <E: Ext>,
|
||||
)?)
|
||||
},
|
||||
|
||||
// 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(ReturnCode::LoggingDisabled)
|
||||
},
|
||||
|
||||
// Stores the current block number of the current contract into the supplied buffer.
|
||||
//
|
||||
// The value is stored to linear memory at the address pointed to by `out_ptr`.
|
||||
@@ -1565,6 +1536,35 @@ define_env!(Env, <E: Ext>,
|
||||
}
|
||||
},
|
||||
|
||||
// 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.
|
||||
[__unstable__] 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(ReturnCode::LoggingDisabled)
|
||||
},
|
||||
|
||||
// Stores the rent params into the supplied buffer.
|
||||
//
|
||||
// The value is stored to linear memory at the address pointed to by `out_ptr`.
|
||||
@@ -1579,10 +1579,38 @@ define_env!(Env, <E: Ext>,
|
||||
// The returned information was collected and cached when the current contract call
|
||||
// started execution. Any change to those values that happens due to actions of the
|
||||
// current call or contracts that are called by this contract are not considered.
|
||||
[seal0] seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
ctx.charge_gas(RuntimeCosts::RentParams)?;
|
||||
//
|
||||
// # Unstable
|
||||
//
|
||||
// This function is unstable and subject to change (or removal) in the future. Do not
|
||||
// deploy a contract using it to a production chain.
|
||||
[__unstable__] seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
Ok(ctx.write_sandbox_output(
|
||||
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
|
||||
)?)
|
||||
},
|
||||
|
||||
// Stores the rent status into the supplied buffer.
|
||||
//
|
||||
// The value is stored to linear memory at the address pointed to by `out_ptr`.
|
||||
// `out_len_ptr` must point to a u32 value that describes the available space at
|
||||
// `out_ptr`. This call overwrites it with the size of the value. If the available
|
||||
// space at `out_ptr` is less than the size of the value a trap is triggered.
|
||||
//
|
||||
// The data is encoded as [`crate::rent::RentStatus`].
|
||||
//
|
||||
// # Parameters
|
||||
//
|
||||
// - `at_refcount`: The refcount assumed for the returned `custom_refcount_*` fields
|
||||
//
|
||||
// # Unstable
|
||||
//
|
||||
// This function is unstable and subject to change (or removal) in the future. Do not
|
||||
// deploy a contract using it to a production chain.
|
||||
[__unstable__] seal_rent_status(ctx, at_refcount: u32, out_ptr: u32, out_len_ptr: u32) => {
|
||||
let rent_status = ctx.ext.rent_status(at_refcount).encode();
|
||||
Ok(ctx.write_sandbox_output(
|
||||
out_ptr, out_len_ptr, &rent_status, false, already_charged
|
||||
)?)
|
||||
},
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user