mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
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:
committed by
GitHub
parent
3743cec9bd
commit
a4e8875897
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -2736,7 +2736,6 @@ fn refcounter() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn reinstrument_does_charge() {
|
||||
let (wasm, code_hash) = compile_module::<Test>("return_with_data").unwrap();
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user