contracts: Expose rent parameter to contracts (#8231)

* contracts: Expose rent parameter to contracts

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Fix typos

* Improve comments

* Add rent parameter weights

* Allow deploying a new schedule with the same version

* Add storage migration for new schedule

* Only decode the schedule version in storage migration

* Remove confusing docs

* Replace original_code_len() by aggregate_code_len()

Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
Alexander Theißen
2021-03-12 12:21:08 +01:00
committed by GitHub
parent 3743cec9bd
commit a4e8875897
11 changed files with 1129 additions and 698 deletions
+7 -1
View File
@@ -16,7 +16,13 @@ The interface provided to smart contracts will adhere to semver with one excepti
major version bumps will be backwards compatible with regard to already deployed contracts.
In other words: Upgrading this pallet will not break pre-existing contracts.
## [v3.0.0]
## [Unreleased]
### Added
- Add `seal_rent_params` contract callable function.
## [v3.0.0] 2021-02-25
This version constitutes the first release that brings any stability guarantees (see above).
@@ -533,6 +533,14 @@ 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>();
+318 -55
View File
@@ -18,14 +18,14 @@
use crate::{
CodeHash, Event, Config, Module as Contracts,
TrieId, BalanceOf, ContractInfo, gas::GasMeter, rent::Rent, storage::{self, Storage},
Error, ContractInfoOf, Schedule,
Error, ContractInfoOf, Schedule, AliveContractInfo,
};
use sp_core::crypto::UncheckedFrom;
use sp_std::{
prelude::*,
marker::PhantomData,
};
use sp_runtime::traits::{Bounded, Zero, Convert, Saturating};
use sp_runtime::{Perbill, traits::{Bounded, Zero, Convert, Saturating}};
use frame_support::{
dispatch::{DispatchResult, DispatchError},
traits::{ExistenceRequirement, Currency, Time, Randomness, Get},
@@ -43,13 +43,82 @@ pub type StorageKey = [u8; 32];
/// A type that represents a topic of an event. At the moment a hash is used.
pub type TopicOf<T> = <T as frame_system::Config>::Hash;
/// Describes whether we deal with a contract or a plain account.
pub enum TransactorKind {
/// Transaction was initiated from a plain account. That can be either be through a
/// signed transaction or through RPC.
PlainAccount,
/// The call was initiated by a contract account.
Contract,
/// Information needed for rent calculations that can be requested by a contract.
#[derive(codec::Encode)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub struct RentParams<T: Config> {
/// The total balance of the contract. Includes the balance transferred from the caller.
total_balance: BalanceOf<T>,
/// The free balance of the contract. Includes the balance transferred from the caller.
free_balance: BalanceOf<T>,
/// See crate [`Contracts::subsistence_threshold()`].
subsistence_threshold: BalanceOf<T>,
/// See crate [`Config::DepositPerContract`].
deposit_per_contract: BalanceOf<T>,
/// See crate [`Config::DepositPerStorageByte`].
deposit_per_storage_byte: BalanceOf<T>,
/// See crate [`Config::DepositPerStorageItem`].
deposit_per_storage_item: BalanceOf<T>,
/// See crate [`Ext::rent_allowance()`].
rent_allowance: BalanceOf<T>,
/// See crate [`Config::RentFraction`].
rent_fraction: Perbill,
/// See crate [`AliveContractInfo::storage_size`].
storage_size: u32,
/// See crate [`Executable::aggregate_code_len()`].
code_size: u32,
/// See crate [`Executable::refcount()`].
code_refcount: u32,
/// Reserved for backwards compatible changes to this data structure.
_reserved: Option<()>,
}
impl<T> RentParams<T>
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
{
fn new<E: Executable<T>>(
account_id: &T::AccountId,
contract: &AliveContractInfo<T>,
executable: &E
) -> Self {
Self {
total_balance: T::Currency::total_balance(account_id),
free_balance: T::Currency::free_balance(account_id),
subsistence_threshold: <Contracts<T>>::subsistence_threshold(),
deposit_per_contract: T::DepositPerContract::get(),
deposit_per_storage_byte: T::DepositPerStorageByte::get(),
deposit_per_storage_item: T::DepositPerStorageItem::get(),
rent_allowance: contract.rent_allowance,
rent_fraction: T::RentFraction::get(),
storage_size: contract.storage_size,
code_size: executable.aggregate_code_len(),
code_refcount: executable.refcount(),
_reserved: None,
}
}
}
/// 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
@@ -198,9 +267,13 @@ pub trait Ext: sealing::Sealed {
/// Get a reference to the schedule used by the current call.
fn schedule(&self) -> &Schedule<Self::T>;
/// Information needed for rent calculations.
fn rent_params(&self) -> &RentParams<Self::T>;
}
/// Describes the different functions that can be exported by an [`Executable`].
#[cfg_attr(test, derive(Clone, Copy, PartialEq))]
pub enum ExportedFunction {
/// The constructor function which is executed on deployment of a contract.
Constructor,
@@ -263,6 +336,7 @@ pub trait Executable<T: Config>: Sized {
/// The storage that is occupied by the instrumented executable and its pristine source.
///
/// The returned size is already divided by the number of users who share the code.
/// This is essentially `aggregate_code_len() / refcount()`.
///
/// # Note
///
@@ -273,16 +347,22 @@ pub trait Executable<T: Config>: Sized {
/// Size of the instrumented code in bytes.
fn code_len(&self) -> u32;
/// Sum of instrumented and pristine code len.
fn aggregate_code_len(&self) -> u32;
// The number of contracts using this executable.
fn refcount(&self) -> u32;
}
pub struct ExecutionContext<'a, T: Config + 'a, E> {
pub caller: Option<&'a ExecutionContext<'a, T, E>>,
pub self_account: T::AccountId,
pub self_trie_id: Option<TrieId>,
pub depth: usize,
pub schedule: &'a Schedule<T>,
pub timestamp: MomentOf<T>,
pub block_number: T::BlockNumber,
caller: Option<&'a ExecutionContext<'a, T, E>>,
self_account: T::AccountId,
self_trie_id: Option<TrieId>,
depth: usize,
schedule: &'a Schedule<T>,
timestamp: MomentOf<T>,
block_number: T::BlockNumber,
_phantom: PhantomData<E>,
}
@@ -371,8 +451,12 @@ where
)?
}
let call_context = nested.new_call_context(
caller, &dest, value, &contract, &executable,
);
let output = executable.execute(
nested.new_call_context(caller, value),
call_context,
&ExportedFunction::Call,
input_data,
gas_meter,
@@ -403,7 +487,7 @@ where
let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
Storage::<T>::place_contract(
let contract = Storage::<T>::place_contract(
&dest,
nested
.self_trie_id
@@ -428,8 +512,16 @@ where
// spawned. This is OK as overcharging is always safe.
let occupied_storage = executable.occupied_storage();
let call_context = nested.new_call_context(
caller.clone(),
&dest,
endowment,
&contract,
&executable,
);
let output = executable.execute(
nested.new_call_context(caller.clone(), endowment),
call_context,
&ExportedFunction::Constructor,
input_data,
gas_meter,
@@ -468,7 +560,10 @@ where
fn new_call_context<'b>(
&'b mut self,
caller: T::AccountId,
dest: &T::AccountId,
value: BalanceOf<T>,
contract: &AliveContractInfo<T>,
executable: &E,
) -> CallContext<'b, 'a, T, E> {
let timestamp = self.timestamp.clone();
let block_number = self.block_number.clone();
@@ -478,6 +573,7 @@ where
value_transferred: value,
timestamp,
block_number,
rent_params: RentParams::new(dest, contract, executable),
_phantom: Default::default(),
}
}
@@ -517,6 +613,15 @@ where
}
}
/// Describes whether we deal with a contract or a plain account.
enum TransactorKind {
/// Transaction was initiated from a plain account. That can be either be through a
/// signed transaction or through RPC.
PlainAccount,
/// The call was initiated by a contract account.
Contract,
}
/// Describes possible transfer causes.
enum TransferCause {
Call,
@@ -581,6 +686,7 @@ struct CallContext<'a, 'b: 'a, T: Config + 'b, E> {
value_transferred: BalanceOf<T>,
timestamp: MomentOf<T>,
block_number: T::BlockNumber,
rent_params: RentParams<T>,
_phantom: PhantomData<E>,
}
@@ -793,6 +899,10 @@ where
fn schedule(&self) -> &Schedule<Self::T> {
&self.ctx.schedule
}
fn rent_params(&self) -> &RentParams<Self::T> {
&self.rent_params
}
}
fn deposit_event<T: Config>(
@@ -834,11 +944,13 @@ mod tests {
ALICE, BOB, CHARLIE,
test_utils::{place_contract, set_balance, get_balance},
},
exec::ExportedFunction::*,
Error, Weight,
};
use sp_runtime::DispatchError;
use assert_matches::assert_matches;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use pretty_assertions::{assert_eq, assert_ne};
type MockContext<'a> = ExecutionContext<'a, Test, MockExecutable>;
@@ -865,7 +977,12 @@ mod tests {
}
#[derive(Clone)]
struct MockExecutable(Rc<dyn Fn(MockCtx) -> ExecResult + 'static>, CodeHash<Test>);
struct MockExecutable {
func: Rc<dyn Fn(MockCtx, &Self) -> ExecResult + 'static>,
func_type: ExportedFunction,
code_hash: CodeHash<Test>,
refcount: u64,
}
#[derive(Default)]
struct MockLoader {
@@ -874,16 +991,61 @@ mod tests {
}
impl MockLoader {
fn insert(f: impl Fn(MockCtx) -> ExecResult + 'static) -> CodeHash<Test> {
fn insert(
func_type: ExportedFunction,
f: impl Fn(MockCtx, &MockExecutable,
) -> ExecResult + 'static) -> CodeHash<Test> {
LOADER.with(|loader| {
let mut loader = loader.borrow_mut();
// Generate code hashes as monotonically increasing values.
let hash = <Test as frame_system::Config>::Hash::from_low_u64_be(loader.counter);
loader.counter += 1;
loader.map.insert(hash, MockExecutable (Rc::new(f), hash.clone()));
loader.map.insert(hash, MockExecutable {
func: Rc::new(f),
func_type,
code_hash: hash.clone(),
refcount: 1,
});
hash
})
}
fn increment_refcount(code_hash: CodeHash<Test>) {
LOADER.with(|loader| {
let mut loader = loader.borrow_mut();
loader.map
.entry(code_hash)
.and_modify(|executable| executable.refcount += 1)
.or_insert_with(|| panic!("code_hash does not exist"));
});
}
fn decrement_refcount(code_hash: CodeHash<Test>) {
use std::collections::hash_map::Entry::Occupied;
LOADER.with(|loader| {
let mut loader = loader.borrow_mut();
let mut entry = match loader.map.entry(code_hash) {
Occupied(e) => e,
_ => panic!("code_hash does not exist"),
};
let refcount = &mut entry.get_mut().refcount;
*refcount -= 1;
if *refcount == 0 {
entry.remove();
}
});
}
fn refcount(code_hash: &CodeHash<Test>) -> u32 {
LOADER.with(|loader| {
loader
.borrow()
.map
.get(code_hash)
.expect("code_hash does not exist")
.refcount()
})
}
}
impl Executable<Test> for MockExecutable {
@@ -905,30 +1067,43 @@ mod tests {
})
}
fn drop_from_storage(self) {}
fn drop_from_storage(self) {
MockLoader::decrement_refcount(self.code_hash);
}
fn add_user(_code_hash: CodeHash<Test>) -> Result<u32, DispatchError> {
fn add_user(code_hash: CodeHash<Test>) -> Result<u32, DispatchError> {
MockLoader::increment_refcount(code_hash);
Ok(0)
}
fn remove_user(_code_hash: CodeHash<Test>) -> u32 { 0 }
fn remove_user(code_hash: CodeHash<Test>) -> u32 {
MockLoader::decrement_refcount(code_hash);
0
}
fn execute<E: Ext<T = Test>>(
self,
mut ext: E,
_function: &ExportedFunction,
function: &ExportedFunction,
input_data: Vec<u8>,
gas_meter: &mut GasMeter<Test>,
) -> ExecResult {
(self.0)(MockCtx {
ext: &mut ext,
input_data,
gas_meter,
})
if let &Constructor = function {
MockLoader::increment_refcount(self.code_hash);
}
if function == &self.func_type {
(self.func)(MockCtx {
ext: &mut ext,
input_data,
gas_meter,
}, &self)
} else {
exec_success()
}
}
fn code_hash(&self) -> &CodeHash<Test> {
&self.1
&self.code_hash
}
fn occupied_storage(&self) -> u32 {
@@ -938,6 +1113,14 @@ mod tests {
fn code_len(&self) -> u32 {
0
}
fn aggregate_code_len(&self) -> u32 {
0
}
fn refcount(&self) -> u32 {
self.refcount as u32
}
}
fn exec_success() -> ExecResult {
@@ -952,7 +1135,7 @@ mod tests {
let value = Default::default();
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let exec_ch = MockLoader::insert(|_ctx| {
let exec_ch = MockLoader::insert(Call, |_ctx, _executable| {
TEST_DATA.with(|data| data.borrow_mut().push(1));
exec_success()
});
@@ -1003,7 +1186,8 @@ mod tests {
let dest = BOB;
let return_ch = MockLoader::insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() })
Call,
|_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: Vec::new() })
);
ExtBuilder::default().build().execute_with(|| {
@@ -1062,7 +1246,8 @@ mod tests {
let origin = ALICE;
let dest = BOB;
let return_ch = MockLoader::insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] })
Call,
|_, _| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![1, 2, 3, 4] })
);
ExtBuilder::default().build().execute_with(|| {
@@ -1090,7 +1275,8 @@ mod tests {
let origin = ALICE;
let dest = BOB;
let return_ch = MockLoader::insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] })
Call,
|_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![1, 2, 3, 4] })
);
ExtBuilder::default().build().execute_with(|| {
@@ -1113,7 +1299,7 @@ mod tests {
#[test]
fn input_data_to_call() {
let input_data_ch = MockLoader::insert(|ctx| {
let input_data_ch = MockLoader::insert(Call, |ctx, _| {
assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
exec_success()
});
@@ -1136,7 +1322,7 @@ mod tests {
#[test]
fn input_data_to_instantiate() {
let input_data_ch = MockLoader::insert(|ctx| {
let input_data_ch = MockLoader::insert(Constructor, |ctx, _| {
assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
exec_success()
});
@@ -1172,7 +1358,7 @@ mod tests {
static REACHED_BOTTOM: RefCell<bool> = RefCell::new(false);
}
let value = Default::default();
let recurse_ch = MockLoader::insert(|ctx| {
let recurse_ch = MockLoader::insert(Call, |ctx, _| {
// Try to call into yourself.
let r = ctx.ext.call(&BOB, 0, ctx.gas_meter, vec![]);
@@ -1222,7 +1408,7 @@ mod tests {
static WITNESSED_CALLER_CHARLIE: RefCell<Option<AccountIdOf<Test>>> = RefCell::new(None);
}
let bob_ch = MockLoader::insert(|ctx| {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
// Record the caller for bob.
WITNESSED_CALLER_BOB.with(|caller|
*caller.borrow_mut() = Some(ctx.ext.caller().clone())
@@ -1235,7 +1421,7 @@ mod tests {
);
exec_success()
});
let charlie_ch = MockLoader::insert(|ctx| {
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
// Record the caller for charlie.
WITNESSED_CALLER_CHARLIE.with(|caller|
*caller.borrow_mut() = Some(ctx.ext.caller().clone())
@@ -1265,7 +1451,7 @@ mod tests {
#[test]
fn address_returns_proper_values() {
let bob_ch = MockLoader::insert(|ctx| {
let bob_ch = MockLoader::insert(Call, |ctx, _| {
// Verify that address matches BOB.
assert_eq!(*ctx.ext.address(), BOB);
@@ -1276,7 +1462,7 @@ mod tests {
);
exec_success()
});
let charlie_ch = MockLoader::insert(|ctx| {
let charlie_ch = MockLoader::insert(Call, |ctx, _| {
assert_eq!(*ctx.ext.address(), CHARLIE);
exec_success()
});
@@ -1300,7 +1486,7 @@ mod tests {
#[test]
fn refuse_instantiate_with_value_below_existential_deposit() {
let dummy_ch = MockLoader::insert(|_| exec_success());
let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success());
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = Contracts::current_schedule();
@@ -1326,7 +1512,8 @@ mod tests {
#[test]
fn instantiation_work_with_success_output() {
let dummy_ch = MockLoader::insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
Constructor,
|_, _| Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: vec![80, 65, 83, 83] })
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
@@ -1361,7 +1548,8 @@ mod tests {
#[test]
fn instantiation_fails_with_failing_output() {
let dummy_ch = MockLoader::insert(
|_| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
Constructor,
|_, _| Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: vec![70, 65, 73, 76] })
);
ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
@@ -1392,12 +1580,12 @@ mod tests {
#[test]
fn instantiation_from_contract() {
let dummy_ch = MockLoader::insert(|_| exec_success());
let dummy_ch = MockLoader::insert(Call, |_, _| exec_success());
let instantiated_contract_address = Rc::new(RefCell::new(None::<AccountIdOf<Test>>));
let instantiator_ch = MockLoader::insert({
let instantiator_ch = MockLoader::insert(Call, {
let dummy_ch = dummy_ch.clone();
let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
move |ctx| {
move |ctx, _| {
// Instantiate a contract and save it's address in `instantiated_contract_address`.
let (address, output, _) = ctx.ext.instantiate(
dummy_ch,
@@ -1436,12 +1624,12 @@ mod tests {
#[test]
fn instantiation_traps() {
let dummy_ch = MockLoader::insert(
|_| Err("It's a trap!".into())
let dummy_ch = MockLoader::insert(Constructor,
|_, _| Err("It's a trap!".into())
);
let instantiator_ch = MockLoader::insert({
let instantiator_ch = MockLoader::insert(Call, {
let dummy_ch = dummy_ch.clone();
move |ctx| {
move |ctx, _| {
// Instantiate a contract and save it's address in `instantiated_contract_address`.
assert_matches!(
ctx.ext.instantiate(
@@ -1481,7 +1669,7 @@ mod tests {
#[test]
fn termination_from_instantiate_fails() {
let terminate_ch = MockLoader::insert(|ctx| {
let terminate_ch = MockLoader::insert(Constructor, |ctx, _| {
ctx.ext.terminate(&ALICE).unwrap();
exec_success()
});
@@ -1518,7 +1706,7 @@ mod tests {
#[test]
fn rent_allowance() {
let rent_allowance_ch = MockLoader::insert(|ctx| {
let rent_allowance_ch = MockLoader::insert(Constructor, |ctx, _| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let allowance = subsistence * 3;
assert_eq!(ctx.ext.rent_allowance(), <BalanceOf<Test>>::max_value());
@@ -1547,4 +1735,79 @@ mod tests {
assert_matches!(result, Ok(_));
});
}
#[test]
fn rent_params_works() {
let code_hash = MockLoader::insert(Call, |ctx, executable| {
let address = ctx.ext.address();
let contract = <ContractInfoOf<Test>>::get(address)
.and_then(|c| c.get_alive())
.unwrap();
assert_eq!(ctx.ext.rent_params(), &RentParams::new(address, &contract, executable));
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 10);
place_contract(&BOB, code_hash);
ctx.call(
BOB,
0,
&mut gas_meter,
vec![],
).unwrap();
});
}
#[test]
fn rent_params_snapshotted() {
let code_hash = MockLoader::insert(Call, |ctx, executable| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let address = ctx.ext.address();
let contract = <ContractInfoOf<Test>>::get(address)
.and_then(|c| c.get_alive())
.unwrap();
let rent_params = RentParams::new(address, &contract, executable);
// Changing the allowance during the call: rent params stay unchanged.
let allowance = 42;
assert_ne!(allowance, rent_params.rent_allowance);
ctx.ext.set_rent_allowance(allowance);
assert_eq!(ctx.ext.rent_params(), &rent_params);
// Creating another instance from the same code_hash increases the refcount.
// This is also not reflected in the rent params.
assert_eq!(MockLoader::refcount(&executable.code_hash), 1);
ctx.ext.instantiate(
executable.code_hash,
subsistence * 25,
&mut GasMeter::<Test>::new(GAS_LIMIT),
vec![],
&[],
).unwrap();
assert_eq!(MockLoader::refcount(&executable.code_hash), 2);
assert_eq!(ctx.ext.rent_params(), &rent_params);
exec_success()
});
ExtBuilder::default().build().execute_with(|| {
let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
set_balance(&ALICE, subsistence * 100);
place_contract(&BOB, code_hash);
ctx.call(
BOB,
subsistence * 50,
&mut gas_meter,
vec![],
).unwrap();
});
}
}
+10 -2
View File
@@ -90,6 +90,7 @@ mod wasm;
mod rent;
mod benchmarking;
mod schedule;
mod migration;
pub mod chain_extension;
pub mod weights;
@@ -265,6 +266,10 @@ pub mod pallet {
Storage::<T>::process_deletion_queue_batch(weight_limit)
.saturating_add(T::WeightInfo::on_initialize())
}
fn on_runtime_upgrade() -> Weight {
migration::migrate::<T>()
}
}
#[pallet::call]
@@ -275,14 +280,17 @@ pub mod pallet {
{
/// Updates the schedule for metering contracts.
///
/// The schedule must have a greater version than the stored schedule.
/// 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 <Module<T>>::current_schedule().version >= schedule.version {
if <Module<T>>::current_schedule().version > schedule.version {
Err(Error::<T>::InvalidScheduleVersion)?
}
Self::deposit_event(Event::ScheduleUpdated(schedule.version));
@@ -0,0 +1,45 @@
// This file is part of Substrate.
// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// 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};
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| {
if let Some(version) = version {
Some(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()
})
} else {
None
}
});
}
_ => (),
}
weight
}
+19
View File
@@ -45,6 +45,14 @@ pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 1_000;
#[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 version: u32,
/// Whether the `seal_println` function is allowed to be used contracts.
@@ -62,6 +70,11 @@ pub struct Schedule<T: Config> {
}
/// 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.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
pub struct Limits {
@@ -188,6 +201,7 @@ pub struct InstructionWeights<T: Config> {
pub i64rotl: u32,
pub i64rotr: u32,
/// The type parameter is used in the default implementation.
#[codec(skip)]
pub _phantom: PhantomData<T>,
}
@@ -348,7 +362,11 @@ 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>
}
@@ -572,6 +590,7 @@ 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,
}
}
+4 -4
View File
@@ -30,7 +30,7 @@ use sp_io::hashing::blake2_256;
use sp_runtime::traits::{Bounded, Saturating, Zero};
use sp_core::crypto::UncheckedFrom;
use frame_support::{
dispatch::DispatchResult,
dispatch::{DispatchError, DispatchResult},
storage::child::{self, KillChildStorageResult},
traits::Get,
weights::Weight,
@@ -162,7 +162,7 @@ where
account: &AccountIdOf<T>,
trie_id: TrieId,
ch: CodeHash<T>,
) -> DispatchResult {
) -> Result<AliveContractInfo<T>, DispatchError> {
<ContractInfoOf<T>>::try_mutate(account, |existing| {
if existing.is_some() {
return Err(Error::<T>::DuplicateContract.into());
@@ -184,9 +184,9 @@ where
_reserved: None,
};
*existing = Some(contract.into());
*existing = Some(contract.clone().into());
Ok(())
Ok(contract)
})
}
-1
View File
@@ -2736,7 +2736,6 @@ fn refcounter() {
});
}
#[test]
fn reinstrument_does_charge() {
let (wasm, code_hash) = compile_module::<Test>("return_with_data").unwrap();
+59 -13
View File
@@ -224,13 +224,21 @@ where
fn occupied_storage(&self) -> u32 {
// We disregard the size of the struct itself as the size is completely
// dominated by the code size.
let len = self.original_code_len.saturating_add(self.code.len() as u32);
let len = self.aggregate_code_len();
len.checked_div(self.refcount as u32).unwrap_or(len)
}
fn code_len(&self) -> u32 {
self.code.len() as u32
}
fn aggregate_code_len(&self) -> u32 {
self.original_code_len.saturating_add(self.code_len())
}
fn refcount(&self) -> u32 {
self.refcount as u32
}
}
#[cfg(test)]
@@ -238,7 +246,7 @@ mod tests {
use super::*;
use crate::{
CodeHash, BalanceOf, Error, Module as Contracts,
exec::{Ext, StorageKey, AccountIdOf, Executable},
exec::{Ext, StorageKey, AccountIdOf, Executable, RentParams},
gas::GasMeter,
tests::{Test, Call, ALICE, BOB},
};
@@ -249,6 +257,7 @@ mod tests {
use frame_support::{dispatch::DispatchResult, weights::Weight};
use assert_matches::assert_matches;
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags, ExecError, ErrorOrigin};
use pretty_assertions::assert_eq;
const GAS_LIMIT: Weight = 10_000_000_000;
@@ -295,6 +304,7 @@ mod tests {
// (topics, data)
events: Vec<(Vec<H256>, Vec<u8>)>,
schedule: Schedule<Test>,
rent_params: RentParams<Test>,
}
impl Ext for MockExt {
@@ -395,46 +405,38 @@ mod tests {
fn value_transferred(&self) -> u64 {
1337
}
fn now(&self) -> &u64 {
&1111
}
fn minimum_balance(&self) -> u64 {
666
}
fn tombstone_deposit(&self) -> u64 {
16
}
fn random(&self, subject: &[u8]) -> H256 {
H256::from_slice(subject)
}
fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) {
self.events.push((topics, data))
}
fn set_rent_allowance(&mut self, rent_allowance: u64) {
self.rent_allowance = rent_allowance;
}
fn rent_allowance(&self) -> u64 {
self.rent_allowance
}
fn block_number(&self) -> u64 { 121 }
fn max_value_size(&self) -> u32 { 16_384 }
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
BalanceOf::<Self::T>::from(1312_u32).saturating_mul(weight.into())
}
fn schedule(&self) -> &Schedule<Self::T> {
&self.schedule
}
fn rent_params(&self) -> &RentParams<Self::T> {
&self.rent_params
}
}
impl Ext for &mut MockExt {
@@ -537,6 +539,9 @@ mod tests {
fn schedule(&self) -> &Schedule<Self::T> {
(**self).schedule()
}
fn rent_params(&self) -> &RentParams<Self::T> {
(**self).rent_params()
}
}
fn execute<E: Ext>(
@@ -1840,4 +1845,45 @@ mod tests {
);
}
const CODE_RENT_PARAMS: &str = r#"
(module
(import "seal0" "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))
;; [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_params
(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"))
)
"#;
#[test]
fn rent_params_work() {
let output = execute(
CODE_RENT_PARAMS,
vec![],
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
let rent_params = <RentParams<Test>>::default().encode();
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
}
}
@@ -215,6 +215,8 @@ pub enum RuntimeToken {
ChainExtension(u64),
/// Weight charged for copying data from the sandbox.
CopyIn(u32),
/// Weight of calling `seal_rent_params`.
RentParams,
}
impl<T: Config> Token<T> for RuntimeToken
@@ -283,6 +285,7 @@ where
.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,
}
}
}
@@ -1513,4 +1516,25 @@ define_env!(Env, <E: Ext>,
})),
}
},
// Stores the rent params 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::exec::RentParams`].
//
// # Note
//
// 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.
seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::RentParams)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
)?)
},
);
File diff suppressed because it is too large Load Diff