contracts: Refactor the exec module (#8604)

* contracts: Add default implementation for Executable::occupied_storage()

* contracts: Refactor the exec module

* Let runtime specify the backing type of the call stack

This removes the need for a runtime check of the specified
`MaxDepth`. We can now garantuee that we don't need to
allocate when a new call frame is pushed.

* Fix doc typo

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* 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

* Review nits

* Fix defect in contract info caching behaviour

* Add more docs

* Fix wording and typos

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Parity Benchmarking Bot <admin@parity.io>
This commit is contained in:
Alexander Theißen
2021-05-07 14:37:30 +02:00
committed by GitHub
parent 17a1997d18
commit 9e894ce135
15 changed files with 2064 additions and 1862 deletions
@@ -137,7 +137,7 @@ where
// in the storage.
//
// We need to re-instrument the code with the latest schedule here.
gas_meter.charge(&(), InstrumentToken(prefab_module.original_code_len))?;
gas_meter.charge(InstrumentToken(prefab_module.original_code_len))?;
private::reinstrument(&mut prefab_module, schedule)?;
}
}
@@ -194,9 +194,7 @@ fn increment_64(refcount: &mut u64) {
struct InstrumentToken(u32);
impl<T: Config> Token<T> for InstrumentToken {
type Metadata = ();
fn calculate_amount(&self, _metadata: &Self::Metadata) -> Weight {
fn weight(&self) -> Weight {
T::WeightInfo::instrument(self.0 / 1024)
}
}
+61 -201
View File
@@ -34,7 +34,7 @@ use sp_std::prelude::*;
use sp_core::crypto::UncheckedFrom;
use codec::{Encode, Decode};
use frame_support::dispatch::DispatchError;
pub use self::runtime::{ReturnCode, Runtime, RuntimeToken};
pub use self::runtime::{ReturnCode, Runtime, RuntimeCosts};
#[cfg(feature = "runtime-benchmarks")]
pub use self::code_cache::reinstrument;
#[cfg(test)]
@@ -172,10 +172,9 @@ where
fn execute<E: Ext<T = T>>(
self,
mut ext: E,
ext: &mut E,
function: &ExportedFunction,
input_data: Vec<u8>,
gas_meter: &mut GasMeter<E::T>,
) -> ExecResult {
let memory =
sp_sandbox::Memory::new(self.initial, Some(self.maximum))
@@ -196,10 +195,9 @@ where
});
let mut runtime = Runtime::new(
&mut ext,
ext,
input_data,
memory,
gas_meter,
);
// We store before executing so that the code hash is available in the constructor.
@@ -220,13 +218,6 @@ where
&self.code_hash
}
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.aggregate_code_len();
len.checked_div(self.refcount as u32).unwrap_or(len)
}
fn code_len(&self) -> u32 {
self.code.len() as u32
}
@@ -260,8 +251,7 @@ mod tests {
use assert_matches::assert_matches;
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
use pretty_assertions::assert_eq;
const GAS_LIMIT: Weight = 10_000_000_000;
use sp_std::borrow::BorrowMut;
#[derive(Debug, PartialEq, Eq)]
struct DispatchEntry(Call);
@@ -295,7 +285,6 @@ mod tests {
data: Vec<u8>,
}
#[derive(Default)]
pub struct MockExt {
storage: HashMap<StorageKey, Vec<u8>>,
rent_allowance: u64,
@@ -307,23 +296,48 @@ mod tests {
events: Vec<(Vec<H256>, Vec<u8>)>,
schedule: Schedule<Test>,
rent_params: RentParams<Test>,
gas_meter: GasMeter<Test>,
}
impl Default for MockExt {
fn default() -> Self {
Self {
storage: Default::default(),
rent_allowance: Default::default(),
instantiates: Default::default(),
terminations: Default::default(),
transfers: Default::default(),
restores: Default::default(),
events: Default::default(),
schedule: Default::default(),
rent_params: Default::default(),
gas_meter: GasMeter::new(10_000_000_000),
}
}
}
impl Ext for MockExt {
type T = Test;
fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned()
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
*self.storage.entry(key).or_insert(Vec::new()) = value.unwrap_or(Vec::new());
Ok(())
fn call(
&mut self,
_gas_limit: Weight,
to: AccountIdOf<Self::T>,
value: u64,
data: Vec<u8>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
self.transfers.push(TransferEntry {
to,
value,
data: data,
});
Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }, 0))
}
fn instantiate(
&mut self,
gas_limit: Weight,
code_hash: CodeHash<Test>,
endowment: u64,
gas_meter: &mut GasMeter<Test>,
data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)> {
@@ -331,7 +345,7 @@ mod tests {
code_hash: code_hash.clone(),
endowment,
data: data.to_vec(),
gas_left: gas_meter.gas_left(),
gas_left: gas_limit,
salt: salt.to_vec(),
});
Ok((
@@ -355,22 +369,6 @@ mod tests {
});
Ok(())
}
fn call(
&mut self,
to: &AccountIdOf<Self::T>,
value: u64,
_gas_meter: &mut GasMeter<Test>,
data: Vec<u8>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
self.transfers.push(TransferEntry {
to: to.clone(),
value,
data: data,
});
// Assume for now that it was just a plain transfer.
// TODO: Add tests for different call outcomes.
Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }, 0))
}
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
@@ -395,6 +393,13 @@ mod tests {
});
Ok((0, 0))
}
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned()
}
fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
*self.storage.entry(key).or_insert(Vec::new()) = value.unwrap_or(Vec::new());
Ok(())
}
fn caller(&self) -> &AccountIdOf<Self::T> {
&ALICE
}
@@ -425,7 +430,7 @@ mod tests {
fn set_rent_allowance(&mut self, rent_allowance: u64) {
self.rent_allowance = rent_allowance;
}
fn rent_allowance(&self) -> u64 {
fn rent_allowance(&mut self) -> u64 {
self.rent_allowance
}
fn block_number(&self) -> u64 { 121 }
@@ -439,127 +444,22 @@ mod tests {
fn rent_params(&self) -> &RentParams<Self::T> {
&self.rent_params
}
}
impl Ext for &mut MockExt {
type T = <MockExt as Ext>::T;
fn get_storage(&self, key: &[u8; 32]) -> Option<Vec<u8>> {
(**self).get_storage(key)
}
fn set_storage(&mut self, key: [u8; 32], value: Option<Vec<u8>>) -> DispatchResult {
(**self).set_storage(key, value)
}
fn instantiate(
&mut self,
code: CodeHash<Test>,
value: u64,
gas_meter: &mut GasMeter<Test>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)> {
(**self).instantiate(code, value, gas_meter, input_data, salt)
}
fn transfer(
&mut self,
to: &AccountIdOf<Self::T>,
value: u64,
) -> Result<(), DispatchError> {
(**self).transfer(to, value)
}
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)> {
(**self).terminate(beneficiary)
}
fn call(
&mut self,
to: &AccountIdOf<Self::T>,
value: u64,
gas_meter: &mut GasMeter<Test>,
input_data: Vec<u8>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
(**self).call(to, value, gas_meter, input_data)
}
fn restore_to(
&mut self,
dest: AccountIdOf<Self::T>,
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
(**self).restore_to(
dest,
code_hash,
rent_allowance,
delta,
)
}
fn caller(&self) -> &AccountIdOf<Self::T> {
(**self).caller()
}
fn address(&self) -> &AccountIdOf<Self::T> {
(**self).address()
}
fn balance(&self) -> u64 {
(**self).balance()
}
fn value_transferred(&self) -> u64 {
(**self).value_transferred()
}
fn now(&self) -> &u64 {
(**self).now()
}
fn minimum_balance(&self) -> u64 {
(**self).minimum_balance()
}
fn tombstone_deposit(&self) -> u64 {
(**self).tombstone_deposit()
}
fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>) {
(**self).random(subject)
}
fn deposit_event(&mut self, topics: Vec<H256>, data: Vec<u8>) {
(**self).deposit_event(topics, data)
}
fn set_rent_allowance(&mut self, rent_allowance: u64) {
(**self).set_rent_allowance(rent_allowance)
}
fn rent_allowance(&self) -> u64 {
(**self).rent_allowance()
}
fn block_number(&self) -> u64 {
(**self).block_number()
}
fn max_value_size(&self) -> u32 {
(**self).max_value_size()
}
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
(**self).get_weight_price(weight)
}
fn schedule(&self) -> &Schedule<Self::T> {
(**self).schedule()
}
fn rent_params(&self) -> &RentParams<Self::T> {
(**self).rent_params()
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.gas_meter
}
}
fn execute<E: Ext>(
fn execute<E: BorrowMut<MockExt>>(
wat: &str,
input_data: Vec<u8>,
ext: E,
gas_meter: &mut GasMeter<E::T>,
mut ext: E,
) -> ExecResult
where
<E::T as frame_system::Config>::AccountId:
UncheckedFrom<<E::T as frame_system::Config>::Hash> + AsRef<[u8]>
{
let wasm = wat::parse_str(wat).unwrap();
let schedule = crate::Schedule::default();
let executable = PrefabWasmModule::<E::T>::from_code(wasm, &schedule).unwrap();
executable.execute(ext, &ExportedFunction::Call, input_data, gas_meter)
let executable = PrefabWasmModule::<<MockExt as Ext>::T>::from_code(wasm, &schedule)
.unwrap();
executable.execute(ext.borrow_mut(), &ExportedFunction::Call, input_data)
}
const CODE_TRANSFER: &str = r#"
@@ -603,7 +503,6 @@ mod tests {
CODE_TRANSFER,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
));
assert_eq!(
@@ -669,7 +568,6 @@ mod tests {
CODE_CALL,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
));
assert_eq!(
@@ -745,7 +643,6 @@ mod tests {
CODE_INSTANTIATE,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
));
assert_matches!(
@@ -794,7 +691,6 @@ mod tests {
CODE_TERMINATE,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
assert_eq!(
@@ -857,7 +753,6 @@ mod tests {
&CODE_TRANSFER_LIMITED_GAS,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
));
assert_eq!(
@@ -945,7 +840,6 @@ mod tests {
CODE_GET_STORAGE,
vec![],
mock_ext,
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
assert_eq!(output, ExecReturnValue {
@@ -1003,7 +897,6 @@ mod tests {
CODE_CALLER,
vec![],
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
));
}
@@ -1056,7 +949,6 @@ mod tests {
CODE_ADDRESS,
vec![],
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
));
}
@@ -1103,12 +995,10 @@ mod tests {
#[test]
fn balance() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_BALANCE,
vec![],
MockExt::default(),
&mut gas_meter,
));
}
@@ -1155,12 +1045,10 @@ mod tests {
#[test]
fn gas_price() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_GAS_PRICE,
vec![],
MockExt::default(),
&mut gas_meter,
));
}
@@ -1205,18 +1093,19 @@ mod tests {
#[test]
fn gas_left() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
let mut ext = MockExt::default();
let gas_limit = ext.gas_meter.gas_left();
let output = execute(
CODE_GAS_LEFT,
vec![],
MockExt::default(),
&mut gas_meter,
&mut ext,
).unwrap();
let gas_left = Weight::decode(&mut &*output.data).unwrap();
assert!(gas_left < GAS_LIMIT, "gas_left must be less than initial");
assert!(gas_left > gas_meter.gas_left(), "gas_left must be greater than final");
let actual_left = ext.gas_meter.gas_left();
assert!(gas_left < gas_limit, "gas_left must be less than initial");
assert!(gas_left > actual_left, "gas_left must be greater than final");
}
const CODE_VALUE_TRANSFERRED: &str = r#"
@@ -1262,12 +1151,10 @@ mod tests {
#[test]
fn value_transferred() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_VALUE_TRANSFERRED,
vec![],
MockExt::default(),
&mut gas_meter,
));
}
@@ -1301,7 +1188,6 @@ mod tests {
CODE_RETURN_FROM_START_FN,
vec![],
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
assert_eq!(
@@ -1356,12 +1242,10 @@ mod tests {
#[test]
fn now() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_TIMESTAMP_NOW,
vec![],
MockExt::default(),
&mut gas_meter,
));
}
@@ -1407,12 +1291,10 @@ mod tests {
#[test]
fn minimum_balance() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_MINIMUM_BALANCE,
vec![],
MockExt::default(),
&mut gas_meter,
));
}
@@ -1458,12 +1340,10 @@ mod tests {
#[test]
fn tombstone_deposit() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_TOMBSTONE_DEPOSIT,
vec![],
MockExt::default(),
&mut gas_meter,
));
}
@@ -1523,13 +1403,10 @@ mod tests {
#[test]
fn random() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
let output = execute(
CODE_RANDOM,
vec![],
MockExt::default(),
&mut gas_meter,
).unwrap();
// The mock ext just returns the same data that was passed as the subject.
@@ -1601,13 +1478,10 @@ mod tests {
#[test]
fn random_v1() {
let mut gas_meter = GasMeter::new(GAS_LIMIT);
let output = execute(
CODE_RANDOM_V1,
vec![],
MockExt::default(),
&mut gas_meter,
).unwrap();
// The mock ext just returns the same data that was passed as the subject.
@@ -1650,12 +1524,10 @@ mod tests {
#[test]
fn deposit_event() {
let mut mock_ext = MockExt::default();
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_ok!(execute(
CODE_DEPOSIT_EVENT,
vec![],
&mut mock_ext,
&mut gas_meter
));
assert_eq!(mock_ext.events, vec![
@@ -1663,7 +1535,7 @@ mod tests {
vec![0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x14, 0x00])
]);
assert!(gas_meter.gas_left() > 0);
assert!(mock_ext.gas_meter.gas_left() > 0);
}
const CODE_DEPOSIT_EVENT_MAX_TOPICS: &str = r#"
@@ -1693,17 +1565,14 @@ mod tests {
)
"#;
/// Checks that the runtime traps if there are more than `max_topic_events` topics.
#[test]
fn deposit_event_max_topics() {
// Checks that the runtime traps if there are more than `max_topic_events` topics.
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_eq!(
execute(
CODE_DEPOSIT_EVENT_MAX_TOPICS,
vec![],
MockExt::default(),
&mut gas_meter
),
Err(ExecError {
error: Error::<Test>::TooManyTopics.into(),
@@ -1738,17 +1607,14 @@ mod tests {
)
"#;
/// Checks that the runtime traps if there are duplicates.
#[test]
fn deposit_event_duplicates() {
// Checks that the runtime traps if there are duplicates.
let mut gas_meter = GasMeter::new(GAS_LIMIT);
assert_eq!(
execute(
CODE_DEPOSIT_EVENT_DUPLICATES,
vec![],
MockExt::default(),
&mut gas_meter
),
Err(ExecError {
error: Error::<Test>::DuplicateTopics.into(),
@@ -1806,7 +1672,6 @@ mod tests {
CODE_BLOCK_NUMBER,
vec![],
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
}
@@ -1848,7 +1713,6 @@ mod tests {
CODE_RETURN_WITH_DATA,
hex!("00000000445566778899").to_vec(),
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
assert_eq!(output, ExecReturnValue {
@@ -1864,7 +1728,6 @@ mod tests {
CODE_RETURN_WITH_DATA,
hex!("010000005566778899").to_vec(),
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
assert_eq!(output, ExecReturnValue {
@@ -1897,7 +1760,6 @@ mod tests {
CODE_OUT_OF_BOUNDS_ACCESS,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
);
assert_eq!(
@@ -1932,7 +1794,6 @@ mod tests {
CODE_DECODE_FAILURE,
vec![],
&mut mock_ext,
&mut GasMeter::new(GAS_LIMIT),
);
assert_eq!(
@@ -1980,7 +1841,6 @@ mod tests {
CODE_RENT_PARAMS,
vec![],
MockExt::default(),
&mut GasMeter::new(GAS_LIMIT),
).unwrap();
let rent_params = Bytes(<RentParams<Test>>::default().encode());
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
+95 -117
View File
@@ -20,7 +20,7 @@
use crate::{
Config, CodeHash, BalanceOf, Error,
exec::{Ext, StorageKey, TopicOf, ExecResult, ExecError},
gas::{GasMeter, Token, ChargedAmount},
gas::{Token, ChargedAmount},
wasm::env_def::ConvertibleToWasm,
schedule::HostFnWeights,
};
@@ -28,7 +28,6 @@ use parity_wasm::elements::ValueType;
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight};
use sp_std::prelude::*;
use codec::{Decode, DecodeAll, Encode};
use sp_runtime::traits::SaturatedConversion;
use sp_core::{Bytes, crypto::UncheckedFrom};
use sp_io::hashing::{
keccak_256,
@@ -132,7 +131,7 @@ impl<T: Into<DispatchError>> From<T> for TrapReason {
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
pub enum RuntimeToken {
pub enum RuntimeCosts {
/// Charge the gas meter with the cost of a metering block. The charged costs are
/// the supplied cost of the block plus the overhead of the metering itself.
MeteringBlock(u32),
@@ -220,15 +219,14 @@ pub enum RuntimeToken {
RentParams,
}
impl<T: Config> Token<T> for RuntimeToken
where
T::AccountId: UncheckedFrom<T::Hash>, T::AccountId: AsRef<[u8]>
{
type Metadata = HostFnWeights<T>;
fn calculate_amount(&self, s: &Self::Metadata) -> Weight {
use self::RuntimeToken::*;
match *self {
impl RuntimeCosts {
fn token<T>(&self, s: &HostFnWeights<T>) -> RuntimeToken
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
use self::RuntimeCosts::*;
let weight = match *self {
MeteringBlock(amount) => s.gas.saturating_add(amount.into()),
Caller => s.caller,
Address => s.address,
@@ -287,14 +285,37 @@ where
ChainExtension(amount) => amount,
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
RentParams => s.rent_params,
};
RuntimeToken {
#[cfg(test)]
_created_from: *self,
weight,
}
}
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)]
struct RuntimeToken {
#[cfg(test)]
_created_from: RuntimeCosts,
weight: Weight,
}
impl<T> Token<T> for RuntimeToken
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
fn weight(&self) -> Weight {
self.weight
}
}
/// This is only appropriate when writing out data of constant size that does not depend on user
/// input. In this case the costs for this copy was already charged as part of the token at
/// the beginning of the API entry point.
fn already_charged(_: u32) -> Option<RuntimeToken> {
fn already_charged(_: u32) -> Option<RuntimeCosts> {
None
}
@@ -303,7 +324,6 @@ pub struct Runtime<'a, E: Ext + 'a> {
ext: &'a mut E,
input_data: Option<Vec<u8>>,
memory: sp_sandbox::Memory,
gas_meter: &'a mut GasMeter<E::T>,
trap_reason: Option<TrapReason>,
}
@@ -317,13 +337,11 @@ where
ext: &'a mut E,
input_data: Vec<u8>,
memory: sp_sandbox::Memory,
gas_meter: &'a mut GasMeter<E::T>,
) -> Self {
Runtime {
ext,
input_data: Some(input_data),
memory,
gas_meter,
trap_reason: None,
}
}
@@ -406,21 +424,16 @@ where
/// Charge the gas meter with the specified token.
///
/// Returns `Err(HostError)` if there is not enough gas.
pub fn charge_gas<Tok>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError>
where
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
{
self.gas_meter.charge(&self.ext.schedule().host_fn_weights, token)
pub fn charge_gas(&mut self, costs: RuntimeCosts) -> Result<ChargedAmount, DispatchError> {
let token = costs.token(&self.ext.schedule().host_fn_weights);
self.ext.gas_meter().charge(token)
}
/// Correct previously charged gas amount.
pub fn adjust_gas<Tok>(&mut self, charged_amount: ChargedAmount, adjusted_amount: Tok)
where
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
{
self.gas_meter.adjust_gas(
pub fn adjust_gas(&mut self, charged_amount: ChargedAmount, adjusted_amount: RuntimeCosts) {
let adjusted_amount = adjusted_amount.token(&self.ext.schedule().host_fn_weights);
self.ext.gas_meter().adjust_gas(
charged_amount,
&self.ext.schedule().host_fn_weights,
adjusted_amount,
);
}
@@ -474,11 +487,11 @@ where
pub fn read_sandbox_memory_as<D: Decode>(&mut self, ptr: u32, len: u32)
-> Result<D, DispatchError>
{
let amount = self.charge_gas(RuntimeToken::CopyIn(len))?;
let amount = self.charge_gas(RuntimeCosts::CopyIn(len))?;
let buf = self.read_sandbox_memory(ptr, len)?;
let decoded = D::decode_all(&mut &buf[..])
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
self.gas_meter.refund(amount);
self.ext.gas_meter().refund(amount);
Ok(decoded)
}
@@ -507,7 +520,7 @@ where
out_len_ptr: u32,
buf: &[u8],
allow_skip: bool,
create_token: impl FnOnce(u32) -> Option<RuntimeToken>,
create_token: impl FnOnce(u32) -> Option<RuntimeCosts>,
) -> Result<(), DispatchError>
{
if allow_skip && out_ptr == u32::max_value() {
@@ -521,8 +534,8 @@ where
Err(Error::<E::T>::OutputBufferTooSmall)?
}
if let Some(token) = create_token(buf_len) {
self.charge_gas(token)?;
if let Some(costs) = create_token(buf_len) {
self.charge_gas(costs)?;
}
self.memory.set(out_ptr, buf).and_then(|_| {
@@ -631,7 +644,7 @@ define_env!(Env, <E: Ext>,
//
// - amount: How much gas is used.
[seal0] gas(ctx, amount: u32) => {
ctx.charge_gas(RuntimeToken::MeteringBlock(amount))?;
ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?;
Ok(())
},
@@ -651,7 +664,7 @@ define_env!(Env, <E: Ext>,
// - If value length exceeds the configured maximum value length of a storage entry.
// - Upon trying to set an empty storage entry (value length is 0).
[seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
ctx.charge_gas(RuntimeToken::SetStorage(value_len))?;
ctx.charge_gas(RuntimeCosts::SetStorage(value_len))?;
if value_len > ctx.ext.max_value_size() {
Err(Error::<E::T>::ValueTooLarge)?;
}
@@ -667,7 +680,7 @@ define_env!(Env, <E: Ext>,
//
// - `key_ptr`: pointer into the linear memory where the location to clear the value is placed.
[seal0] seal_clear_storage(ctx, key_ptr: u32) => {
ctx.charge_gas(RuntimeToken::ClearStorage)?;
ctx.charge_gas(RuntimeCosts::ClearStorage)?;
let mut key: StorageKey = [0; 32];
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
ctx.ext.set_storage(key, None).map_err(Into::into)
@@ -686,12 +699,12 @@ define_env!(Env, <E: Ext>,
//
// `ReturnCode::KeyNotFound`
[seal0] seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
ctx.charge_gas(RuntimeToken::GetStorageBase)?;
ctx.charge_gas(RuntimeCosts::GetStorageBase)?;
let mut key: StorageKey = [0; 32];
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
if let Some(value) = ctx.ext.get_storage(&key) {
ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, |len| {
Some(RuntimeToken::GetStorageCopyOut(len))
Some(RuntimeCosts::GetStorageCopyOut(len))
})?;
Ok(ReturnCode::Success)
} else {
@@ -721,7 +734,7 @@ define_env!(Env, <E: Ext>,
value_ptr: u32,
value_len: u32
) -> ReturnCode => {
ctx.charge_gas(RuntimeToken::Transfer)?;
ctx.charge_gas(RuntimeCosts::Transfer)?;
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(account_ptr, account_len)?;
let value: BalanceOf<<E as Ext>::T> =
@@ -780,45 +793,27 @@ define_env!(Env, <E: Ext>,
output_ptr: u32,
output_len_ptr: u32
) -> ReturnCode => {
ctx.charge_gas(RuntimeToken::CallBase(input_data_len))?;
ctx.charge_gas(RuntimeCosts::CallBase(input_data_len))?;
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(callee_ptr, callee_len)?;
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr, value_len)?;
let input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
if value > 0u32.into() {
ctx.charge_gas(RuntimeToken::CallSurchargeTransfer)?;
ctx.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
}
let charged = ctx.charge_gas(
RuntimeToken::CallSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
RuntimeCosts::CallSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
)?;
let nested_gas_limit = if gas == 0 {
ctx.gas_meter.gas_left()
} else {
gas.saturated_into()
};
let ext = &mut ctx.ext;
let call_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
match nested_meter {
Some(nested_meter) => {
ext.call(
&callee,
value,
nested_meter,
input_data,
)
}
// there is not enough gas to allocate for the nested call.
None => Err((Error::<<E as Ext>::T>::OutOfGas.into(), 0)),
}
});
let call_outcome = ext.call(gas, callee, value, input_data);
let code_len = match &call_outcome {
Ok((_, len)) => len,
Err((_, len)) => len,
};
ctx.adjust_gas(charged, RuntimeToken::CallSurchargeCodeSize(*code_len));
ctx.adjust_gas(charged, RuntimeCosts::CallSurchargeCodeSize(*code_len));
if let Ok((output, _)) = &call_outcome {
ctx.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
Some(RuntimeToken::CallCopyOut(len))
Some(RuntimeCosts::CallCopyOut(len))
})?;
}
Ok(Runtime::<E>::exec_into_return_code(call_outcome.map(|r| r.0).map_err(|r| r.0))?)
@@ -885,41 +880,22 @@ define_env!(Env, <E: Ext>,
salt_ptr: u32,
salt_len: u32
) -> ReturnCode => {
ctx.charge_gas(RuntimeToken::InstantiateBase {input_data_len, salt_len})?;
ctx.charge_gas(RuntimeCosts::InstantiateBase {input_data_len, salt_len})?;
let code_hash: CodeHash<<E as Ext>::T> =
ctx.read_sandbox_memory_as(code_hash_ptr, code_hash_len)?;
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr, value_len)?;
let input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
let salt = ctx.read_sandbox_memory(salt_ptr, salt_len)?;
let charged = ctx.charge_gas(
RuntimeToken::InstantiateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
RuntimeCosts::InstantiateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
)?;
let nested_gas_limit = if gas == 0 {
ctx.gas_meter.gas_left()
} else {
gas.saturated_into()
};
let ext = &mut ctx.ext;
let instantiate_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
match nested_meter {
Some(nested_meter) => {
ext.instantiate(
code_hash,
value,
nested_meter,
input_data,
&salt,
)
}
// there is not enough gas to allocate for the nested call.
None => Err((Error::<<E as Ext>::T>::OutOfGas.into(), 0)),
}
});
let instantiate_outcome = ext.instantiate(gas, code_hash, value, input_data, &salt);
let code_len = match &instantiate_outcome {
Ok((_, _, code_len)) => code_len,
Err((_, code_len)) => code_len,
};
ctx.adjust_gas(charged, RuntimeToken::InstantiateSurchargeCodeSize(*code_len));
ctx.adjust_gas(charged, RuntimeCosts::InstantiateSurchargeCodeSize(*code_len));
if let Ok((address, output, _)) = &instantiate_outcome {
if !output.flags.contains(ReturnFlags::REVERT) {
ctx.write_sandbox_output(
@@ -927,7 +903,7 @@ define_env!(Env, <E: Ext>,
)?;
}
ctx.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
Some(RuntimeToken::InstantiateCopyOut(len))
Some(RuntimeCosts::InstantiateCopyOut(len))
})?;
}
Ok(Runtime::<E>::exec_into_return_code(
@@ -956,18 +932,18 @@ define_env!(Env, <E: Ext>,
beneficiary_ptr: u32,
beneficiary_len: u32
) => {
ctx.charge_gas(RuntimeToken::Terminate)?;
ctx.charge_gas(RuntimeCosts::Terminate)?;
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
let charged = ctx.charge_gas(
RuntimeToken::TerminateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
RuntimeCosts::TerminateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
)?;
let (result, code_len) = match ctx.ext.terminate(&beneficiary) {
Ok(len) => (Ok(()), len),
Err((err, len)) => (Err(err), len),
};
ctx.adjust_gas(charged, RuntimeToken::TerminateSurchargeCodeSize(code_len));
ctx.adjust_gas(charged, RuntimeCosts::TerminateSurchargeCodeSize(code_len));
result?;
Err(TrapReason::Termination)
},
@@ -983,10 +959,10 @@ define_env!(Env, <E: Ext>,
//
// This function can only be called once. Calling it multiple times will trigger a trap.
[seal0] seal_input(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::InputBase)?;
ctx.charge_gas(RuntimeCosts::InputBase)?;
if let Some(input) = ctx.input_data.take() {
ctx.write_sandbox_output(out_ptr, out_len_ptr, &input, false, |len| {
Some(RuntimeToken::InputCopyOut(len))
Some(RuntimeCosts::InputCopyOut(len))
})?;
Ok(())
} else {
@@ -1012,7 +988,7 @@ define_env!(Env, <E: Ext>,
//
// Using a reserved bit triggers a trap.
[seal0] seal_return(ctx, flags: u32, data_ptr: u32, data_len: u32) => {
ctx.charge_gas(RuntimeToken::Return(data_len))?;
ctx.charge_gas(RuntimeCosts::Return(data_len))?;
Err(TrapReason::Return(ReturnData {
flags,
data: ctx.read_sandbox_memory(data_ptr, data_len)?,
@@ -1030,7 +1006,7 @@ define_env!(Env, <E: Ext>,
// extrinsic will be returned. Otherwise, if this call is initiated by another contract then the
// address of the contract will be returned. The value is encoded as T::AccountId.
[seal0] seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Caller)?;
ctx.charge_gas(RuntimeCosts::Caller)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged
)?)
@@ -1043,7 +1019,7 @@ define_env!(Env, <E: Ext>,
// `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.
[seal0] seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Address)?;
ctx.charge_gas(RuntimeCosts::Address)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged
)?)
@@ -1063,7 +1039,7 @@ define_env!(Env, <E: Ext>,
// It is recommended to avoid specifying very small values for `gas` as the prices for a single
// gas can be smaller than one.
[seal0] seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::WeightToFee)?;
ctx.charge_gas(RuntimeCosts::WeightToFee)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, already_charged
)?)
@@ -1078,9 +1054,10 @@ define_env!(Env, <E: Ext>,
//
// The data is encoded as Gas.
[seal0] seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::GasLeft)?;
ctx.charge_gas(RuntimeCosts::GasLeft)?;
let gas_left = &ctx.ext.gas_meter().gas_left().encode();
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, already_charged
out_ptr, out_len_ptr, &gas_left, false, already_charged,
)?)
},
@@ -1093,7 +1070,7 @@ define_env!(Env, <E: Ext>,
//
// The data is encoded as T::Balance.
[seal0] seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Balance)?;
ctx.charge_gas(RuntimeCosts::Balance)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged
)?)
@@ -1108,7 +1085,7 @@ define_env!(Env, <E: Ext>,
//
// The data is encoded as T::Balance.
[seal0] seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::ValueTransferred)?;
ctx.charge_gas(RuntimeCosts::ValueTransferred)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, already_charged
)?)
@@ -1127,7 +1104,7 @@ define_env!(Env, <E: Ext>,
//
// This function is deprecated. Users should migrate to the version in the "seal1" module.
[seal0] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Random)?;
ctx.charge_gas(RuntimeCosts::Random)?;
if subject_len > ctx.ext.schedule().limits.subject_len {
Err(Error::<E::T>::RandomSubjectTooLong)?;
}
@@ -1159,7 +1136,7 @@ define_env!(Env, <E: Ext>,
// call this on later blocks until the block number returned is later than the latest
// commitment.
[seal1] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Random)?;
ctx.charge_gas(RuntimeCosts::Random)?;
if subject_len > ctx.ext.schedule().limits.subject_len {
Err(Error::<E::T>::RandomSubjectTooLong)?;
}
@@ -1176,7 +1153,7 @@ define_env!(Env, <E: Ext>,
// `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.
[seal0] seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Now)?;
ctx.charge_gas(RuntimeCosts::Now)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged
)?)
@@ -1186,7 +1163,7 @@ define_env!(Env, <E: Ext>,
//
// The data is encoded as T::Balance.
[seal0] seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::MinimumBalance)?;
ctx.charge_gas(RuntimeCosts::MinimumBalance)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged
)?)
@@ -1208,7 +1185,7 @@ define_env!(Env, <E: Ext>,
// below the sum of existential deposit and the tombstone deposit. The sum
// is commonly referred as subsistence threshold in code.
[seal0] seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::TombstoneDeposit)?;
ctx.charge_gas(RuntimeCosts::TombstoneDeposit)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, already_charged
)?)
@@ -1256,7 +1233,7 @@ define_env!(Env, <E: Ext>,
delta_ptr: u32,
delta_count: u32
) => {
ctx.charge_gas(RuntimeToken::RestoreTo(delta_count))?;
ctx.charge_gas(RuntimeCosts::RestoreTo(delta_count))?;
let dest: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(dest_ptr, dest_len)?;
let code_hash: CodeHash<<E as Ext>::T> =
@@ -1290,7 +1267,7 @@ define_env!(Env, <E: Ext>,
};
let max_len = <E::T as Config>::MaxCodeSize::get();
let charged = ctx.charge_gas(RuntimeToken::RestoreToSurchargeCodeSize {
let charged = ctx.charge_gas(RuntimeCosts::RestoreToSurchargeCodeSize {
caller_code: max_len,
tombstone_code: max_len,
})?;
@@ -1300,7 +1277,7 @@ define_env!(Env, <E: Ext>,
Ok((code, tomb)) => (Ok(()), code, tomb),
Err((err, code, tomb)) => (Err(err), code, tomb),
};
ctx.adjust_gas(charged, RuntimeToken::RestoreToSurchargeCodeSize {
ctx.adjust_gas(charged, RuntimeCosts::RestoreToSurchargeCodeSize {
caller_code,
tombstone_code,
});
@@ -1341,7 +1318,7 @@ define_env!(Env, <E: Ext>,
let num_topic = topics_len
.checked_div(sp_std::mem::size_of::<TopicOf<E::T>>() as u32)
.ok_or_else(|| "Zero sized topics are not allowed")?;
ctx.charge_gas(RuntimeToken::DepositEvent {
ctx.charge_gas(RuntimeCosts::DepositEvent {
num_topic,
len: data_len,
})?;
@@ -1379,7 +1356,7 @@ define_env!(Env, <E: Ext>,
// Should be decodable as a `T::Balance`. Traps otherwise.
// - value_len: length of the value buffer.
[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
ctx.charge_gas(RuntimeToken::SetRentAllowance)?;
ctx.charge_gas(RuntimeCosts::SetRentAllowance)?;
let value: BalanceOf<<E as Ext>::T> =
ctx.read_sandbox_memory_as(value_ptr, value_len)?;
ctx.ext.set_rent_allowance(value);
@@ -1396,9 +1373,10 @@ define_env!(Env, <E: Ext>,
//
// The data is encoded as T::Balance.
[seal0] seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::RentAllowance)?;
ctx.charge_gas(RuntimeCosts::RentAllowance)?;
let rent_allowance = ctx.ext.rent_allowance().encode();
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, already_charged
out_ptr, out_len_ptr, &rent_allowance, false, already_charged
)?)
},
@@ -1420,7 +1398,7 @@ define_env!(Env, <E: Ext>,
// `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.
[seal0] seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::BlockNumber)?;
ctx.charge_gas(RuntimeCosts::BlockNumber)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged
)?)
@@ -1447,7 +1425,7 @@ define_env!(Env, <E: Ext>,
// data is placed. The function will write the result
// directly into this buffer.
[seal0] seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashSha256(input_len))?;
ctx.charge_gas(RuntimeCosts::HashSha256(input_len))?;
Ok(ctx.compute_hash_on_intermediate_buffer(sha2_256, input_ptr, input_len, output_ptr)?)
},
@@ -1472,7 +1450,7 @@ define_env!(Env, <E: Ext>,
// data is placed. The function will write the result
// directly into this buffer.
[seal0] seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashKeccak256(input_len))?;
ctx.charge_gas(RuntimeCosts::HashKeccak256(input_len))?;
Ok(ctx.compute_hash_on_intermediate_buffer(keccak_256, input_ptr, input_len, output_ptr)?)
},
@@ -1497,7 +1475,7 @@ define_env!(Env, <E: Ext>,
// data is placed. The function will write the result
// directly into this buffer.
[seal0] seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashBlake256(input_len))?;
ctx.charge_gas(RuntimeCosts::HashBlake256(input_len))?;
Ok(ctx.compute_hash_on_intermediate_buffer(blake2_256, input_ptr, input_len, output_ptr)?)
},
@@ -1522,7 +1500,7 @@ define_env!(Env, <E: Ext>,
// data is placed. The function will write the result
// directly into this buffer.
[seal0] seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashBlake128(input_len))?;
ctx.charge_gas(RuntimeCosts::HashBlake128(input_len))?;
Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?)
},
@@ -1574,7 +1552,7 @@ define_env!(Env, <E: Ext>,
// 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(RuntimeToken::RentParams)?;
ctx.charge_gas(RuntimeCosts::RentParams)?;
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
)?)