contracts: Consider contract size in weights (#8086)

* contracts: Consider contract size in weights

* Bump spec version

* Whitespace fix

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

* Correct pre-charged code weight even in the error case

* Use the instrumented code size in weight calculation

* Charge the cost of re-instrumentation from the gas meter

* Fix benchmark

* 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

* Better documentation of return types

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-02-22 09:52:58 +01:00
committed by GitHub
parent fbd3148bba
commit 84071d6d49
13 changed files with 1267 additions and 843 deletions
+4 -2
View File
@@ -112,8 +112,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to 0. If only runtime // and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as // implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version. // is and increment impl_version.
spec_version: 264, spec_version: 265,
impl_version: 1, impl_version: 0,
apis: RUNTIME_API_VERSIONS, apis: RUNTIME_API_VERSIONS,
transaction_version: 2, transaction_version: 2,
}; };
@@ -735,6 +735,7 @@ parameter_types! {
<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) - <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(1) -
<Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0) <Runtime as pallet_contracts::Config>::WeightInfo::on_initialize_per_queue_item(0)
)) / 5) as u32; )) / 5) as u32;
pub MaxCodeSize: u32 = 128 * 1024;
} }
impl pallet_contracts::Config for Runtime { impl pallet_contracts::Config for Runtime {
@@ -757,6 +758,7 @@ impl pallet_contracts::Config for Runtime {
type ChainExtension = (); type ChainExtension = ();
type DeletionQueueDepth = DeletionQueueDepth; type DeletionQueueDepth = DeletionQueueDepth;
type DeletionWeightLimit = DeletionWeightLimit; type DeletionWeightLimit = DeletionWeightLimit;
type MaxCodeSize = MaxCodeSize;
} }
impl pallet_sudo::Config for Runtime { impl pallet_sudo::Config for Runtime {
@@ -27,12 +27,14 @@
use crate::Config; use crate::Config;
use crate::Module as Contracts; use crate::Module as Contracts;
use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; use parity_wasm::elements::{
Instruction, Instructions, FuncBody, ValueType, BlockType, Section, CustomSection,
};
use pwasm_utils::stack_height::inject_limiter; use pwasm_utils::stack_height::inject_limiter;
use sp_core::crypto::UncheckedFrom; use sp_core::crypto::UncheckedFrom;
use sp_runtime::traits::Hash; use sp_runtime::traits::Hash;
use sp_sandbox::{EnvironmentDefinitionBuilder, Memory}; use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};
use sp_std::{prelude::*, convert::TryFrom}; use sp_std::{prelude::*, convert::TryFrom, borrow::ToOwned};
/// Pass to `create_code` in order to create a compiled `WasmModule`. /// Pass to `create_code` in order to create a compiled `WasmModule`.
/// ///
@@ -66,6 +68,10 @@ pub struct ModuleDefinition {
pub inject_stack_metering: bool, pub inject_stack_metering: bool,
/// Create a table containing function pointers. /// Create a table containing function pointers.
pub table: Option<TableSegment>, pub table: Option<TableSegment>,
/// Create a section named "dummy" of the specified size. This is useful in order to
/// benchmark the overhead of loading and storing codes of specified sizes. The dummy
/// section only contributes to the size of the contract but does not affect execution.
pub dummy_section: u32,
} }
pub struct TableSegment { pub struct TableSegment {
@@ -204,6 +210,15 @@ where
.build(); .build();
} }
// Add the dummy section
if def.dummy_section > 0 {
contract = contract.with_section(
Section::Custom(
CustomSection::new("dummy".to_owned(), vec![42; def.dummy_section as usize])
)
);
}
let mut code = contract.build(); let mut code = contract.build();
// Inject stack height metering // Inject stack height metering
@@ -235,10 +250,11 @@ where
ModuleDefinition::default().into() ModuleDefinition::default().into()
} }
/// Same as `dummy` but with maximum sized linear memory. /// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
pub fn dummy_with_mem() -> Self { pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
ModuleDefinition { ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()), memory: Some(ImportedMemory::max::<T>()),
dummy_section: dummy_bytes,
.. Default::default() .. Default::default()
} }
.into() .into()
@@ -304,6 +304,19 @@ benchmarks! {
Storage::<T>::process_deletion_queue_batch(Weight::max_value()) Storage::<T>::process_deletion_queue_batch(Weight::max_value())
} }
// This benchmarks the additional weight that is charged when a contract is executed the
// first time after a new schedule was deployed: For every new schedule a contract needs
// to re-run the instrumentation once.
instrument {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c * 1024);
Contracts::<T>::store_code_raw(code)?;
let mut module = PrefabWasmModule::from_storage_noinstr(hash)?;
let schedule = Contracts::<T>::current_schedule();
}: {
Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
}
// This extrinsic is pretty much constant as it is only a simple setter. // This extrinsic is pretty much constant as it is only a simple setter.
update_schedule { update_schedule {
let schedule = Schedule { let schedule = Schedule {
@@ -318,8 +331,13 @@ benchmarks! {
// determine the contract address. // determine the contract address.
// `c`: Size of the code in kilobytes. // `c`: Size of the code in kilobytes.
// `s`: Size of the salt in kilobytes. // `s`: Size of the salt in kilobytes.
//
// # Note
//
// We cannot let `c` grow to the maximum code size because the code is not allowed
// to be larger than the maximum size **after instrumentation**.
instantiate_with_code { instantiate_with_code {
let c in 0 .. Contracts::<T>::current_schedule().limits.code_size / 1024; let c in 0 .. Perbill::from_percent(50).mul_ceil(T::MaxCodeSize::get() / 1024);
let s in 0 .. code::max_pages::<T>() * 64; let s in 0 .. code::max_pages::<T>() * 64;
let salt = vec![42u8; (s * 1024) as usize]; let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into(); let endowment = caller_funding::<T>() / 3u32.into();
@@ -339,14 +357,16 @@ benchmarks! {
} }
// Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate.
// `c`: Size of the code in kilobytes.
// `s`: Size of the salt in kilobytes. // `s`: Size of the salt in kilobytes.
instantiate { instantiate {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let s in 0 .. code::max_pages::<T>() * 64; let s in 0 .. code::max_pages::<T>() * 64;
let salt = vec![42u8; (s * 1024) as usize]; let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into(); let endowment = caller_funding::<T>() / 3u32.into();
let caller = whitelisted_caller(); let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, caller_funding::<T>()); T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_mem(); let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
let origin = RawOrigin::Signed(caller.clone()); let origin = RawOrigin::Signed(caller.clone());
let addr = Contracts::<T>::contract_address(&caller, &hash, &salt); let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
Contracts::<T>::store_code_raw(code)?; Contracts::<T>::store_code_raw(code)?;
@@ -365,10 +385,12 @@ benchmarks! {
// won't call `seal_input` in its constructor to copy the data to contract memory. // won't call `seal_input` in its constructor to copy the data to contract memory.
// The dummy contract used here does not do this. The costs for the data copy is billed as // The dummy contract used here does not do this. The costs for the data copy is billed as
// part of `seal_input`. // part of `seal_input`.
// `c`: Size of the code in kilobytes.
call { call {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let data = vec![42u8; 1024]; let data = vec![42u8; 1024];
let instance = Contract::<T>::with_caller( let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::dummy_with_mem(), vec![], Endow::CollectRent whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), vec![], Endow::CollectRent
)?; )?;
let value = T::Currency::minimum_balance() * 100u32.into(); let value = T::Currency::minimum_balance() * 100u32.into();
let origin = RawOrigin::Signed(instance.caller.clone()); let origin = RawOrigin::Signed(instance.caller.clone());
@@ -396,9 +418,11 @@ benchmarks! {
// will be distributed over multiple blocks using a scheduler. Otherwise there is // will be distributed over multiple blocks using a scheduler. Otherwise there is
// no incentive to remove large contracts when the removal is more expensive than // no incentive to remove large contracts when the removal is more expensive than
// the reward for removing them. // the reward for removing them.
// `c`: Size of the code of the contract that should be evicted.
claim_surcharge { claim_surcharge {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let instance = Contract::<T>::with_caller( let instance = Contract::<T>::with_caller(
whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), vec![], Endow::CollectRent
)?; )?;
let origin = RawOrigin::Signed(instance.caller.clone()); let origin = RawOrigin::Signed(instance.caller.clone());
let account_id = instance.account_id.clone(); let account_id = instance.account_id.clone();
@@ -694,6 +718,42 @@ benchmarks! {
} }
} }
seal_terminate_per_code_kb {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
let beneficiary_bytes = beneficiary.encode();
let beneficiary_len = beneficiary_bytes.len();
let code = WasmModule::<T>::from(ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
name: "seal_terminate",
params: vec![ValueType::I32, ValueType::I32],
return_type: None,
}],
data_segments: vec![
DataSegment {
offset: 0,
value: beneficiary_bytes,
},
],
call_body: Some(body::repeated(1, &[
Instruction::I32Const(0), // beneficiary_ptr
Instruction::I32Const(beneficiary_len as i32), // beneficiary_len
Instruction::Call(0),
])),
dummy_section: c * 1024,
.. Default::default()
});
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
let origin = RawOrigin::Signed(instance.caller.clone());
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
assert_eq!(T::Currency::total_balance(&instance.account_id), Endow::max::<T>());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
verify {
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
assert_eq!(T::Currency::total_balance(&beneficiary), Endow::max::<T>());
}
seal_restore_to { seal_restore_to {
let r in 0 .. 1; let r in 0 .. 1;
@@ -772,9 +832,16 @@ benchmarks! {
} }
} }
seal_restore_to_per_delta { // `c`: Code size of caller contract
// `t`: Code size of tombstone contract
// `d`: Number of supplied delta keys
seal_restore_to_per_code_kb_delta {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let t in 0 .. T::MaxCodeSize::get() / 1024;
let d in 0 .. API_BENCHMARK_BATCHES; let d in 0 .. API_BENCHMARK_BATCHES;
let mut tombstone = ContractWithStorage::<T>::new(0, 0)?; let mut tombstone = ContractWithStorage::<T>::with_code(
WasmModule::<T>::dummy_with_bytes(t * 1024), 0, 0
)?;
tombstone.evict()?; tombstone.evict()?;
let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?; let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?;
@@ -837,6 +904,7 @@ benchmarks! {
Instruction::Call(0), Instruction::Call(0),
Instruction::End, Instruction::End,
])), ])),
dummy_section: c * 1024,
.. Default::default() .. Default::default()
}); });
@@ -1225,7 +1293,7 @@ benchmarks! {
// We call unique accounts. // We call unique accounts.
seal_call { seal_call {
let r in 0 .. API_BENCHMARK_BATCHES; let r in 0 .. API_BENCHMARK_BATCHES;
let dummy_code = WasmModule::<T>::dummy_with_mem(); let dummy_code = WasmModule::<T>::dummy_with_bytes(0);
let callees = (0..r * API_BENCHMARK_BATCH_SIZE) let callees = (0..r * API_BENCHMARK_BATCH_SIZE)
.map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![], Endow::Max)) .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![], Endow::Max))
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
@@ -1280,7 +1348,8 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone()); let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![]) }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
seal_call_per_transfer_input_output_kb { seal_call_per_code_transfer_input_output_kb {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let t in 0 .. 1; let t in 0 .. 1;
let i in 0 .. code::max_pages::<T>() * 64; let i in 0 .. code::max_pages::<T>() * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64; let o in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1302,6 +1371,7 @@ benchmarks! {
Instruction::Call(0), Instruction::Call(0),
Instruction::End, Instruction::End,
])), ])),
dummy_section: c * 1024,
.. Default::default() .. Default::default()
}); });
let callees = (0..API_BENCHMARK_BATCH_SIZE) let callees = (0..API_BENCHMARK_BATCH_SIZE)
@@ -1475,7 +1545,8 @@ benchmarks! {
} }
} }
seal_instantiate_per_input_output_salt_kb { seal_instantiate_per_code_input_output_salt_kb {
let c in 0 .. T::MaxCodeSize::get() / 1024;
let i in 0 .. (code::max_pages::<T>() - 1) * 64; let i in 0 .. (code::max_pages::<T>() - 1) * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64; let o in 0 .. (code::max_pages::<T>() - 1) * 64;
let s in 0 .. (code::max_pages::<T>() - 1) * 64; let s in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1497,6 +1568,7 @@ benchmarks! {
Instruction::Call(0), Instruction::Call(0),
Instruction::End, Instruction::End,
])), ])),
dummy_section: c * 1024,
.. Default::default() .. Default::default()
}); });
let hash = callee_code.hash.clone(); let hash = callee_code.hash.clone();
@@ -2440,8 +2512,6 @@ benchmarks! {
}: {} }: {}
} }
impl_benchmark_test_suite!( impl_benchmark_test_suite!(
Contracts, Contracts,
crate::tests::ExtBuilder::default().build(), crate::tests::ExtBuilder::default().build(),
+124 -53
View File
@@ -72,8 +72,13 @@ pub trait Ext {
/// Instantiate a contract from the given code. /// Instantiate a contract from the given code.
/// ///
/// Returns the original code size of the called contract.
/// The newly created account will be associated with `code`. `value` specifies the amount of value /// The newly created account will be associated with `code`. `value` specifies the amount of value
/// transferred from this to the newly created account (also known as endowment). /// transferred from this to the newly created account (also known as endowment).
///
/// # Return Value
///
/// Result<(AccountId, ExecReturnValue, CodeSize), (ExecError, CodeSize)>
fn instantiate( fn instantiate(
&mut self, &mut self,
code: CodeHash<Self::T>, code: CodeHash<Self::T>,
@@ -81,7 +86,7 @@ pub trait Ext {
gas_meter: &mut GasMeter<Self::T>, gas_meter: &mut GasMeter<Self::T>,
input_data: Vec<u8>, input_data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError>; ) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)>;
/// Transfer some amount of funds into the specified account. /// Transfer some amount of funds into the specified account.
fn transfer( fn transfer(
@@ -92,24 +97,35 @@ pub trait Ext {
/// Transfer all funds to `beneficiary` and delete the contract. /// Transfer all funds to `beneficiary` and delete the contract.
/// ///
/// Returns the original code size of the terminated contract.
/// Since this function removes the self contract eagerly, if succeeded, no further actions should /// Since this function removes the self contract eagerly, if succeeded, no further actions should
/// be performed on this `Ext` instance. /// be performed on this `Ext` instance.
/// ///
/// This function will fail if the same contract is present on the contract /// This function will fail if the same contract is present on the contract
/// call stack. /// call stack.
///
/// # Return Value
///
/// Result<CodeSize, (DispatchError, CodeSize)>
fn terminate( fn terminate(
&mut self, &mut self,
beneficiary: &AccountIdOf<Self::T>, beneficiary: &AccountIdOf<Self::T>,
) -> DispatchResult; ) -> Result<u32, (DispatchError, u32)>;
/// Call (possibly transferring some amount of funds) into the specified account. /// Call (possibly transferring some amount of funds) into the specified account.
///
/// Returns the original code size of the called contract.
///
/// # Return Value
///
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
fn call( fn call(
&mut self, &mut self,
to: &AccountIdOf<Self::T>, to: &AccountIdOf<Self::T>,
value: BalanceOf<Self::T>, value: BalanceOf<Self::T>,
gas_meter: &mut GasMeter<Self::T>, gas_meter: &mut GasMeter<Self::T>,
input_data: Vec<u8>, input_data: Vec<u8>,
) -> ExecResult; ) -> Result<(ExecReturnValue, u32), (ExecError, u32)>;
/// Restores the given destination contract sacrificing the current one. /// Restores the given destination contract sacrificing the current one.
/// ///
@@ -118,13 +134,17 @@ pub trait Ext {
/// ///
/// This function will fail if the same contract is present /// This function will fail if the same contract is present
/// on the contract call stack. /// on the contract call stack.
///
/// # Return Value
///
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
fn restore_to( fn restore_to(
&mut self, &mut self,
dest: AccountIdOf<Self::T>, dest: AccountIdOf<Self::T>,
code_hash: CodeHash<Self::T>, code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>, rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>, delta: Vec<StorageKey>,
) -> DispatchResult; ) -> Result<(u32, u32), (DispatchError, u32, u32)>;
/// Returns a reference to the account id of the caller. /// Returns a reference to the account id of the caller.
fn caller(&self) -> &AccountIdOf<Self::T>; fn caller(&self) -> &AccountIdOf<Self::T>;
@@ -190,7 +210,11 @@ pub enum ExportedFunction {
/// order to be able to mock the wasm logic for testing. /// order to be able to mock the wasm logic for testing.
pub trait Executable<T: Config>: Sized { pub trait Executable<T: Config>: Sized {
/// Load the executable from storage. /// Load the executable from storage.
fn from_storage(code_hash: CodeHash<T>, schedule: &Schedule<T>) -> Result<Self, DispatchError>; fn from_storage(
code_hash: CodeHash<T>,
schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError>;
/// Load the module from storage without re-instrumenting it. /// Load the module from storage without re-instrumenting it.
/// ///
@@ -203,10 +227,14 @@ pub trait Executable<T: Config>: Sized {
fn drop_from_storage(self); fn drop_from_storage(self);
/// Increment the refcount by one. Fails if the code does not exist on-chain. /// Increment the refcount by one. Fails if the code does not exist on-chain.
fn add_user(code_hash: CodeHash<T>) -> DispatchResult; ///
/// Returns the size of the original code.
fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError>;
/// Decrement the refcount by one and remove the code when it drops to zero. /// Decrement the refcount by one and remove the code when it drops to zero.
fn remove_user(code_hash: CodeHash<T>); ///
/// Returns the size of the original code.
fn remove_user(code_hash: CodeHash<T>) -> u32;
/// Execute the specified exported function and return the result. /// Execute the specified exported function and return the result.
/// ///
@@ -238,6 +266,9 @@ pub trait Executable<T: Config>: Sized {
/// without refetching this from storage the result can be inaccurate as it might be /// without refetching this from storage the result can be inaccurate as it might be
/// working with a stale value. Usually this inaccuracy is tolerable. /// working with a stale value. Usually this inaccuracy is tolerable.
fn occupied_storage(&self) -> u32; fn occupied_storage(&self) -> u32;
/// Size of the instrumented code in bytes.
fn code_len(&self) -> u32;
} }
pub struct ExecutionContext<'a, T: Config + 'a, E> { pub struct ExecutionContext<'a, T: Config + 'a, E> {
@@ -290,35 +321,42 @@ where
} }
/// Make a call to the specified address, optionally transferring some funds. /// Make a call to the specified address, optionally transferring some funds.
///
/// # Return Value
///
/// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
pub fn call( pub fn call(
&mut self, &mut self,
dest: T::AccountId, dest: T::AccountId,
value: BalanceOf<T>, value: BalanceOf<T>,
gas_meter: &mut GasMeter<T>, gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>, input_data: Vec<u8>,
) -> ExecResult { ) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
if self.depth == T::MaxDepth::get() as usize { if self.depth == T::MaxDepth::get() as usize {
Err(Error::<T>::MaxCallDepthReached)? return Err((Error::<T>::MaxCallDepthReached.into(), 0));
} }
let contract = <ContractInfoOf<T>>::get(&dest) let contract = <ContractInfoOf<T>>::get(&dest)
.and_then(|contract| contract.get_alive()) .and_then(|contract| contract.get_alive())
.ok_or(Error::<T>::NotCallable)?; .ok_or((Error::<T>::NotCallable.into(), 0))?;
let executable = E::from_storage(contract.code_hash, &self.schedule)?; let executable = E::from_storage(contract.code_hash, &self.schedule, gas_meter)
.map_err(|e| (e.into(), 0))?;
let code_len = executable.code_len();
// This charges the rent and denies access to a contract that is in need of // This charges the rent and denies access to a contract that is in need of
// eviction by returning `None`. We cannot evict eagerly here because those // eviction by returning `None`. We cannot evict eagerly here because those
// changes would be rolled back in case this contract is called by another // changes would be rolled back in case this contract is called by another
// contract. // contract.
// See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324 // See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324
let contract = Rent::<T, E>::charge(&dest, contract, executable.occupied_storage())? let contract = Rent::<T, E>::charge(&dest, contract, executable.occupied_storage())
.ok_or(Error::<T>::NotCallable)?; .map_err(|e| (e.into(), code_len))?
.ok_or((Error::<T>::NotCallable.into(), code_len))?;
let transactor_kind = self.transactor_kind(); let transactor_kind = self.transactor_kind();
let caller = self.self_account.clone(); let caller = self.self_account.clone();
self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| { let result = self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| {
if value > BalanceOf::<T>::zero() { if value > BalanceOf::<T>::zero() {
transfer::<T>( transfer::<T>(
TransferCause::Call, TransferCause::Call,
@@ -336,7 +374,8 @@ where
gas_meter, gas_meter,
).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; ).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
Ok(output) Ok(output)
}) }).map_err(|e| (e, code_len))?;
Ok((result, code_len))
} }
pub fn instantiate( pub fn instantiate(
@@ -581,10 +620,13 @@ where
gas_meter: &mut GasMeter<T>, gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>, input_data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> { ) -> Result<(AccountIdOf<T>, ExecReturnValue, u32), (ExecError, u32)> {
let executable = E::from_storage(code_hash, &self.ctx.schedule)?; let executable = E::from_storage(code_hash, &self.ctx.schedule, gas_meter)
let result = self.ctx.instantiate(endowment, gas_meter, executable, input_data, salt)?; .map_err(|e| (e.into(), 0))?;
Ok(result) let code_len = executable.code_len();
self.ctx.instantiate(endowment, gas_meter, executable, input_data, salt)
.map(|r| (r.0, r.1, code_len))
.map_err(|e| (e, code_len))
} }
fn transfer( fn transfer(
@@ -604,12 +646,12 @@ where
fn terminate( fn terminate(
&mut self, &mut self,
beneficiary: &AccountIdOf<Self::T>, beneficiary: &AccountIdOf<Self::T>,
) -> DispatchResult { ) -> Result<u32, (DispatchError, u32)> {
let self_id = self.ctx.self_account.clone(); let self_id = self.ctx.self_account.clone();
let value = T::Currency::free_balance(&self_id); let value = T::Currency::free_balance(&self_id);
if let Some(caller_ctx) = self.ctx.caller { if let Some(caller_ctx) = self.ctx.caller {
if caller_ctx.is_live(&self_id) { if caller_ctx.is_live(&self_id) {
return Err(Error::<T>::ReentranceDenied.into()); return Err((Error::<T>::ReentranceDenied.into(), 0));
} }
} }
transfer::<T>( transfer::<T>(
@@ -618,12 +660,12 @@ where
&self_id, &self_id,
beneficiary, beneficiary,
value, value,
)?; ).map_err(|e| (e, 0))?;
if let Some(ContractInfo::Alive(info)) = ContractInfoOf::<T>::take(&self_id) { if let Some(ContractInfo::Alive(info)) = ContractInfoOf::<T>::take(&self_id) {
Storage::<T>::queue_trie_for_deletion(&info)?; Storage::<T>::queue_trie_for_deletion(&info).map_err(|e| (e, 0))?;
E::remove_user(info.code_hash); let code_len = E::remove_user(info.code_hash);
Contracts::<T>::deposit_event(RawEvent::Terminated(self_id, beneficiary.clone())); Contracts::<T>::deposit_event(RawEvent::Terminated(self_id, beneficiary.clone()));
Ok(()) Ok(code_len)
} else { } else {
panic!( panic!(
"this function is only invoked by in the context of a contract;\ "this function is only invoked by in the context of a contract;\
@@ -639,7 +681,7 @@ where
value: BalanceOf<T>, value: BalanceOf<T>,
gas_meter: &mut GasMeter<T>, gas_meter: &mut GasMeter<T>,
input_data: Vec<u8>, input_data: Vec<u8>,
) -> ExecResult { ) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
self.ctx.call(to.clone(), value, gas_meter, input_data) self.ctx.call(to.clone(), value, gas_meter, input_data)
} }
@@ -649,10 +691,10 @@ where
code_hash: CodeHash<Self::T>, code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>, rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>, delta: Vec<StorageKey>,
) -> DispatchResult { ) -> Result<(u32, u32), (DispatchError, u32, u32)> {
if let Some(caller_ctx) = self.ctx.caller { if let Some(caller_ctx) = self.ctx.caller {
if caller_ctx.is_live(&self.ctx.self_account) { if caller_ctx.is_live(&self.ctx.self_account) {
return Err(Error::<T>::ReentranceDenied.into()); return Err((Error::<T>::ReentranceDenied.into(), 0, 0));
} }
} }
@@ -828,7 +870,8 @@ mod tests {
impl Executable<Test> for MockExecutable { impl Executable<Test> for MockExecutable {
fn from_storage( fn from_storage(
code_hash: CodeHash<Test>, code_hash: CodeHash<Test>,
_schedule: &Schedule<Test> _schedule: &Schedule<Test>,
_gas_meter: &mut GasMeter<Test>,
) -> Result<Self, DispatchError> { ) -> Result<Self, DispatchError> {
Self::from_storage_noinstr(code_hash) Self::from_storage_noinstr(code_hash)
} }
@@ -845,11 +888,11 @@ mod tests {
fn drop_from_storage(self) {} fn drop_from_storage(self) {}
fn add_user(_code_hash: CodeHash<Test>) -> DispatchResult { fn add_user(_code_hash: CodeHash<Test>) -> Result<u32, DispatchError> {
Ok(()) Ok(0)
} }
fn remove_user(_code_hash: CodeHash<Test>) {} fn remove_user(_code_hash: CodeHash<Test>) -> u32 { 0 }
fn execute<E: Ext<T = Test>>( fn execute<E: Ext<T = Test>>(
self, self,
@@ -872,6 +915,10 @@ mod tests {
fn occupied_storage(&self) -> u32 { fn occupied_storage(&self) -> u32 {
0 0
} }
fn code_len(&self) -> u32 {
0
}
} }
fn exec_success() -> ExecResult { fn exec_success() -> ExecResult {
@@ -954,7 +1001,7 @@ mod tests {
vec![], vec![],
).unwrap(); ).unwrap();
assert!(!output.is_success()); assert!(!output.0.is_success());
assert_eq!(get_balance(&origin), 100); assert_eq!(get_balance(&origin), 100);
// the rent is still charged // the rent is still charged
@@ -1012,8 +1059,8 @@ mod tests {
); );
let output = result.unwrap(); let output = result.unwrap();
assert!(output.is_success()); assert!(output.0.is_success());
assert_eq!(output.data, vec![1, 2, 3, 4]); assert_eq!(output.0.data, vec![1, 2, 3, 4]);
}); });
} }
@@ -1040,8 +1087,8 @@ mod tests {
); );
let output = result.unwrap(); let output = result.unwrap();
assert!(!output.is_success()); assert!(!output.0.is_success());
assert_eq!(output.data, vec![1, 2, 3, 4]); assert_eq!(output.0.data, vec![1, 2, 3, 4]);
}); });
} }
@@ -1080,13 +1127,17 @@ mod tests {
let schedule = Contracts::current_schedule(); let schedule = Contracts::current_schedule();
let subsistence = Contracts::<Test>::subsistence_threshold(); let subsistence = Contracts::<Test>::subsistence_threshold();
let mut ctx = MockContext::top_level(ALICE, &schedule); let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
input_data_ch, &schedule, &mut gas_meter
).unwrap();
set_balance(&ALICE, subsistence * 10); set_balance(&ALICE, subsistence * 10);
let result = ctx.instantiate( let result = ctx.instantiate(
subsistence * 3, subsistence * 3,
&mut GasMeter::<Test>::new(GAS_LIMIT), &mut gas_meter,
MockExecutable::from_storage(input_data_ch, &schedule).unwrap(), executable,
vec![1, 2, 3, 4], vec![1, 2, 3, 4],
&[], &[],
); );
@@ -1113,7 +1164,7 @@ mod tests {
// Verify that we've got proper error and set `reached_bottom`. // Verify that we've got proper error and set `reached_bottom`.
assert_eq!( assert_eq!(
r, r,
Err(Error::<Test>::MaxCallDepthReached.into()) Err((Error::<Test>::MaxCallDepthReached.into(), 0))
); );
*reached_bottom = true; *reached_bottom = true;
} else { } else {
@@ -1235,12 +1286,16 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| { ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = Contracts::current_schedule(); let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule); let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
).unwrap();
assert_matches!( assert_matches!(
ctx.instantiate( ctx.instantiate(
0, // <- zero endowment 0, // <- zero endowment
&mut GasMeter::<Test>::new(GAS_LIMIT), &mut gas_meter,
MockExecutable::from_storage(dummy_ch, &schedule).unwrap(), executable,
vec![], vec![],
&[], &[],
), ),
@@ -1258,13 +1313,17 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| { ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = Contracts::current_schedule(); let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule); let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
).unwrap();
set_balance(&ALICE, 1000); set_balance(&ALICE, 1000);
let instantiated_contract_address = assert_matches!( let instantiated_contract_address = assert_matches!(
ctx.instantiate( ctx.instantiate(
100, 100,
&mut GasMeter::<Test>::new(GAS_LIMIT), &mut gas_meter,
MockExecutable::from_storage(dummy_ch, &schedule).unwrap(), executable,
vec![], vec![],
&[], &[],
), ),
@@ -1289,13 +1348,17 @@ mod tests {
ExtBuilder::default().existential_deposit(15).build().execute_with(|| { ExtBuilder::default().existential_deposit(15).build().execute_with(|| {
let schedule = Contracts::current_schedule(); let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule); let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
dummy_ch, &schedule, &mut gas_meter
).unwrap();
set_balance(&ALICE, 1000); set_balance(&ALICE, 1000);
let instantiated_contract_address = assert_matches!( let instantiated_contract_address = assert_matches!(
ctx.instantiate( ctx.instantiate(
100, 100,
&mut GasMeter::<Test>::new(GAS_LIMIT), &mut gas_meter,
MockExecutable::from_storage(dummy_ch, &schedule).unwrap(), executable,
vec![], vec![],
&[], &[],
), ),
@@ -1317,7 +1380,7 @@ mod tests {
let instantiated_contract_address = Rc::clone(&instantiated_contract_address); 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`. // Instantiate a contract and save it's address in `instantiated_contract_address`.
let (address, output) = ctx.ext.instantiate( let (address, output, _) = ctx.ext.instantiate(
dummy_ch, dummy_ch,
Contracts::<Test>::subsistence_threshold() * 3, Contracts::<Test>::subsistence_threshold() * 3,
ctx.gas_meter, ctx.gas_meter,
@@ -1369,10 +1432,10 @@ mod tests {
vec![], vec![],
&[], &[],
), ),
Err(ExecError { Err((ExecError {
error: DispatchError::Other("It's a trap!"), error: DispatchError::Other("It's a trap!"),
origin: ErrorOrigin::Callee, origin: ErrorOrigin::Callee,
}) }, 0))
); );
exec_success() exec_success()
@@ -1410,13 +1473,17 @@ mod tests {
.execute_with(|| { .execute_with(|| {
let schedule = Contracts::current_schedule(); let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule); let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
terminate_ch, &schedule, &mut gas_meter
).unwrap();
set_balance(&ALICE, 1000); set_balance(&ALICE, 1000);
assert_eq!( assert_eq!(
ctx.instantiate( ctx.instantiate(
100, 100,
&mut GasMeter::<Test>::new(GAS_LIMIT), &mut gas_meter,
MockExecutable::from_storage(terminate_ch, &schedule).unwrap(), executable,
vec![], vec![],
&[], &[],
), ),
@@ -1445,12 +1512,16 @@ mod tests {
let subsistence = Contracts::<Test>::subsistence_threshold(); let subsistence = Contracts::<Test>::subsistence_threshold();
let schedule = Contracts::current_schedule(); let schedule = Contracts::current_schedule();
let mut ctx = MockContext::top_level(ALICE, &schedule); let mut ctx = MockContext::top_level(ALICE, &schedule);
let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
let executable = MockExecutable::from_storage(
rent_allowance_ch, &schedule, &mut gas_meter
).unwrap();
set_balance(&ALICE, subsistence * 10); set_balance(&ALICE, subsistence * 10);
let result = ctx.instantiate( let result = ctx.instantiate(
subsistence * 5, subsistence * 5,
&mut GasMeter::<Test>::new(GAS_LIMIT), &mut gas_meter,
MockExecutable::from_storage(rent_allowance_ch, &schedule).unwrap(), executable,
vec![], vec![],
&[], &[],
); );
+34 -29
View File
@@ -15,14 +15,17 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use crate::Config; use crate::{Config, Error};
use sp_std::marker::PhantomData; use sp_std::marker::PhantomData;
use sp_runtime::traits::Zero; use sp_runtime::traits::Zero;
use frame_support::{ use frame_support::{
dispatch::{DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo}, dispatch::{
DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo, DispatchError,
},
weights::Weight, weights::Weight,
}; };
use pallet_contracts_primitives::ExecError; use pallet_contracts_primitives::ExecError;
use sp_core::crypto::UncheckedFrom;
#[cfg(test)] #[cfg(test)]
use std::{any::Any, fmt::Debug}; use std::{any::Any, fmt::Debug};
@@ -30,22 +33,6 @@ use std::{any::Any, fmt::Debug};
// Gas is essentially the same as weight. It is a 1 to 1 correspondence. // Gas is essentially the same as weight. It is a 1 to 1 correspondence.
pub type Gas = Weight; pub type Gas = Weight;
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub enum GasMeterResult {
Proceed(ChargedAmount),
OutOfGas,
}
impl GasMeterResult {
pub fn is_out_of_gas(&self) -> bool {
match *self {
GasMeterResult::OutOfGas => true,
GasMeterResult::Proceed(_) => false,
}
}
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ChargedAmount(Gas); pub struct ChargedAmount(Gas);
@@ -103,7 +90,11 @@ pub struct GasMeter<T: Config> {
#[cfg(test)] #[cfg(test)]
tokens: Vec<ErasedToken>, tokens: Vec<ErasedToken>,
} }
impl<T: Config> GasMeter<T> {
impl<T: Config> GasMeter<T>
where
T::AccountId: UncheckedFrom<<T as frame_system::Config>::Hash> + AsRef<[u8]>
{
pub fn new(gas_limit: Gas) -> Self { pub fn new(gas_limit: Gas) -> Self {
GasMeter { GasMeter {
gas_limit, gas_limit,
@@ -128,7 +119,7 @@ impl<T: Config> GasMeter<T> {
&mut self, &mut self,
metadata: &Tok::Metadata, metadata: &Tok::Metadata,
token: Tok, token: Tok,
) -> GasMeterResult { ) -> Result<ChargedAmount, DispatchError> {
#[cfg(test)] #[cfg(test)]
{ {
// Unconditionally add the token to the storage. // Unconditionally add the token to the storage.
@@ -149,11 +140,25 @@ impl<T: Config> GasMeter<T> {
self.gas_left = new_value.unwrap_or_else(Zero::zero); self.gas_left = new_value.unwrap_or_else(Zero::zero);
match new_value { match new_value {
Some(_) => GasMeterResult::Proceed(ChargedAmount(amount)), Some(_) => Ok(ChargedAmount(amount)),
None => GasMeterResult::OutOfGas, None => Err(Error::<T>::OutOfGas.into()),
} }
} }
/// Adjust a previously charged amount down to its actual amount.
///
/// This is when a maximum a priori amount was charged and then should be partially
/// refunded to match the actual amount.
pub fn adjust_gas<Tok: Token<T>>(
&mut self,
charged_amount: ChargedAmount,
metadata: &Tok::Metadata,
token: Tok,
) {
let adjustment = charged_amount.0.saturating_sub(token.calculate_amount(metadata));
self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
}
/// Refund previously charged gas back to the gas meter. /// Refund previously charged gas back to the gas meter.
/// ///
/// This can be used if a gas worst case estimation must be charged before /// This can be used if a gas worst case estimation must be charged before
@@ -304,7 +309,7 @@ mod tests {
let result = gas_meter let result = gas_meter
.charge(&MultiplierTokenMetadata { multiplier: 3 }, MultiplierToken(10)); .charge(&MultiplierTokenMetadata { multiplier: 3 }, MultiplierToken(10));
assert!(!result.is_out_of_gas()); assert!(!result.is_err());
assert_eq!(gas_meter.gas_left(), 49_970); assert_eq!(gas_meter.gas_left(), 49_970);
} }
@@ -312,10 +317,10 @@ mod tests {
#[test] #[test]
fn tracing() { fn tracing() {
let mut gas_meter = GasMeter::<Test>::new(50000); let mut gas_meter = GasMeter::<Test>::new(50000);
assert!(!gas_meter.charge(&(), SimpleToken(1)).is_out_of_gas()); assert!(!gas_meter.charge(&(), SimpleToken(1)).is_err());
assert!(!gas_meter assert!(!gas_meter
.charge(&MultiplierTokenMetadata { multiplier: 3 }, MultiplierToken(10)) .charge(&MultiplierTokenMetadata { multiplier: 3 }, MultiplierToken(10))
.is_out_of_gas()); .is_err());
let mut tokens = gas_meter.tokens()[0..2].iter(); let mut tokens = gas_meter.tokens()[0..2].iter();
match_tokens!(tokens, SimpleToken(1), MultiplierToken(10),); match_tokens!(tokens, SimpleToken(1), MultiplierToken(10),);
@@ -325,7 +330,7 @@ mod tests {
#[test] #[test]
fn refuse_to_execute_anything_if_zero() { fn refuse_to_execute_anything_if_zero() {
let mut gas_meter = GasMeter::<Test>::new(0); let mut gas_meter = GasMeter::<Test>::new(0);
assert!(gas_meter.charge(&(), SimpleToken(1)).is_out_of_gas()); assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());
} }
// Make sure that if the gas meter is charged by exceeding amount then not only an error // Make sure that if the gas meter is charged by exceeding amount then not only an error
@@ -338,10 +343,10 @@ mod tests {
let mut gas_meter = GasMeter::<Test>::new(200); let mut gas_meter = GasMeter::<Test>::new(200);
// The first charge is should lead to OOG. // The first charge is should lead to OOG.
assert!(gas_meter.charge(&(), SimpleToken(300)).is_out_of_gas()); assert!(gas_meter.charge(&(), SimpleToken(300)).is_err());
// The gas meter is emptied at this moment, so this should also fail. // The gas meter is emptied at this moment, so this should also fail.
assert!(gas_meter.charge(&(), SimpleToken(1)).is_out_of_gas()); assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());
} }
@@ -350,6 +355,6 @@ mod tests {
#[test] #[test]
fn charge_exact_amount() { fn charge_exact_amount() {
let mut gas_meter = GasMeter::<Test>::new(25); let mut gas_meter = GasMeter::<Test>::new(25);
assert!(!gas_meter.charge(&(), SimpleToken(25)).is_out_of_gas()); assert!(!gas_meter.charge(&(), SimpleToken(25)).is_err());
} }
} }
+69 -65
View File
@@ -80,7 +80,7 @@
//! * [Balances](../pallet_balances/index.html) //! * [Balances](../pallet_balances/index.html)
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit="256")] #![cfg_attr(feature = "runtime-benchmarks", recursion_limit="512")]
#[macro_use] #[macro_use]
mod gas; mod gas;
@@ -126,9 +126,9 @@ use frame_support::{
}; };
use frame_system::{ensure_signed, ensure_root, Module as System}; use frame_system::{ensure_signed, ensure_root, Module as System};
use pallet_contracts_primitives::{ use pallet_contracts_primitives::{
RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult, ExecResult, RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult,
}; };
use frame_support::weights::Weight; use frame_support::weights::{Weight, PostDispatchInfo, WithPostDispatchInfo};
pub type CodeHash<T> = <T as frame_system::Config>::Hash; pub type CodeHash<T> = <T as frame_system::Config>::Hash;
pub type TrieId = Vec<u8>; pub type TrieId = Vec<u8>;
@@ -344,6 +344,11 @@ pub trait Config: frame_system::Config {
/// The maximum amount of weight that can be consumed per block for lazy trie removal. /// The maximum amount of weight that can be consumed per block for lazy trie removal.
type DeletionWeightLimit: Get<Weight>; type DeletionWeightLimit: Get<Weight>;
/// The maximum length of a contract code in bytes. This limit applies to the instrumented
/// version of the code. Therefore `instantiate_with_code` can fail even when supplying
/// a wasm binary below this maximum size.
type MaxCodeSize: Get<u32>;
} }
decl_error! { decl_error! {
@@ -538,7 +543,7 @@ decl_module! {
/// * If the account is a regular account, any value will be transferred. /// * If the account is a regular account, any value will be transferred.
/// * If no account exists and the call value is not less than `existential_deposit`, /// * If no account exists and the call value is not less than `existential_deposit`,
/// a regular account will be created and any value will be transferred. /// a regular account will be created and any value will be transferred.
#[weight = T::WeightInfo::call().saturating_add(*gas_limit)] #[weight = T::WeightInfo::call(T::MaxCodeSize::get() / 1024).saturating_add(*gas_limit)]
pub fn call( pub fn call(
origin, origin,
dest: <T::Lookup as StaticLookup>::Source, dest: <T::Lookup as StaticLookup>::Source,
@@ -549,10 +554,13 @@ decl_module! {
let origin = ensure_signed(origin)?; let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?; let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit); let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| { let schedule = <Module<T>>::current_schedule();
ctx.call(dest, value, gas_meter, data) let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
}); let (result, code_len) = match ctx.call(dest, value, &mut gas_meter, data) {
gas_meter.into_dispatch_result(result, T::WeightInfo::call()) Ok((output, len)) => (Ok(output), len),
Err((err, len)) => (Err(err), len),
};
gas_meter.into_dispatch_result(result, T::WeightInfo::call(code_len / 1024))
} }
/// Instantiates a new contract from the supplied `code` optionally transferring /// Instantiates a new contract from the supplied `code` optionally transferring
@@ -592,16 +600,16 @@ decl_module! {
salt: Vec<u8>, salt: Vec<u8>,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?; let origin = ensure_signed(origin)?;
let schedule = <Module<T>>::current_schedule();
let code_len = code.len() as u32; let code_len = code.len() as u32;
ensure!(code_len <= schedule.limits.code_size, Error::<T>::CodeTooLarge); ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
let mut gas_meter = GasMeter::new(gas_limit); let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| { let schedule = <Module<T>>::current_schedule();
let executable = PrefabWasmModule::from_code(code, &schedule)?; let executable = PrefabWasmModule::from_code(code, &schedule)?;
let result = ctx.instantiate(endowment, gas_meter, executable, data, &salt) let code_len = executable.code_len();
.map(|(_address, output)| output)?; ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
Ok(result) let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
}); let result = ctx.instantiate(endowment, &mut gas_meter, executable, data, &salt)
.map(|(_address, output)| output);
gas_meter.into_dispatch_result( gas_meter.into_dispatch_result(
result, result,
T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024) T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024)
@@ -614,8 +622,8 @@ decl_module! {
/// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary
/// must be supplied. /// must be supplied.
#[weight = #[weight =
T::WeightInfo::instantiate(salt.len() as u32 / 1024) T::WeightInfo::instantiate(T::MaxCodeSize::get() / 1024, salt.len() as u32 / 1024)
.saturating_add(*gas_limit) .saturating_add(*gas_limit)
] ]
pub fn instantiate( pub fn instantiate(
origin, origin,
@@ -627,15 +635,15 @@ decl_module! {
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?; let origin = ensure_signed(origin)?;
let mut gas_meter = GasMeter::new(gas_limit); let mut gas_meter = GasMeter::new(gas_limit);
let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| { let schedule = <Module<T>>::current_schedule();
let executable = PrefabWasmModule::from_storage(code_hash, &ctx.schedule)?; let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?;
let result = ctx.instantiate(endowment, gas_meter, executable, data, &salt) let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
.map(|(_address, output)| output)?; let code_len = executable.code_len();
Ok(result) let result = ctx.instantiate(endowment, &mut gas_meter, executable, data, &salt)
}); .map(|(_address, output)| output);
gas_meter.into_dispatch_result( gas_meter.into_dispatch_result(
result, result,
T::WeightInfo::instantiate(salt.len() as u32 / 1024) T::WeightInfo::instantiate(code_len / 1024, salt.len() as u32 / 1024),
) )
} }
@@ -648,7 +656,7 @@ decl_module! {
/// ///
/// If contract is not evicted as a result of this call, [`Error::ContractNotEvictable`] /// If contract is not evicted as a result of this call, [`Error::ContractNotEvictable`]
/// is returned and the sender is not eligible for the reward. /// is returned and the sender is not eligible for the reward.
#[weight = T::WeightInfo::claim_surcharge()] #[weight = T::WeightInfo::claim_surcharge(T::MaxCodeSize::get() / 1024)]
pub fn claim_surcharge( pub fn claim_surcharge(
origin, origin,
dest: T::AccountId, dest: T::AccountId,
@@ -675,23 +683,26 @@ decl_module! {
}; };
// If poking the contract has lead to eviction of the contract, give out the rewards. // If poking the contract has lead to eviction of the contract, give out the rewards.
if let Some(rent_payed) = match Rent::<T, PrefabWasmModule<T>>::try_eviction(&dest, handicap)? {
Rent::<T, PrefabWasmModule<T>>::try_eviction(&dest, handicap)? (Some(rent_payed), code_len) => {
{ T::Currency::deposit_into_existing(
T::Currency::deposit_into_existing( &rewarded,
&rewarded, T::SurchargeReward::get().min(rent_payed),
T::SurchargeReward::get().min(rent_payed), )
) .map(|_| PostDispatchInfo {
.map(|_| Pays::No.into()) actual_weight: Some(T::WeightInfo::claim_surcharge(code_len / 1024)),
.map_err(Into::into) pays_fee: Pays::No,
} else { })
Err(Error::<T>::ContractNotEvictable.into()) .map_err(Into::into)
}
(None, code_len) => Err(Error::<T>::ContractNotEvictable.with_weight(
T::WeightInfo::claim_surcharge(code_len / 1024)
)),
} }
} }
} }
} }
/// Public APIs provided by the contracts module.
impl<T: Config> Module<T> impl<T: Config> Module<T>
where where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>, T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
@@ -710,12 +721,12 @@ where
input_data: Vec<u8>, input_data: Vec<u8>,
) -> ContractExecResult { ) -> ContractExecResult {
let mut gas_meter = GasMeter::new(gas_limit); let mut gas_meter = GasMeter::new(gas_limit);
let exec_result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| { let schedule = <Module<T>>::current_schedule();
ctx.call(dest, value, gas_meter, input_data) let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
}); let result = ctx.call(dest, value, &mut gas_meter, input_data);
let gas_consumed = gas_meter.gas_spent(); let gas_consumed = gas_meter.gas_spent();
ContractExecResult { ContractExecResult {
exec_result, exec_result: result.map(|r| r.0).map_err(|r| r.0),
gas_consumed, gas_consumed,
} }
} }
@@ -731,18 +742,12 @@ where
Ok(maybe_value) Ok(maybe_value)
} }
/// Query how many blocks the contract stays alive given that the amount endowment
/// and consumed storage does not change.
pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> { pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> {
Rent::<T, PrefabWasmModule<T>>::compute_projection(&address) Rent::<T, PrefabWasmModule<T>>::compute_projection(&address)
} }
/// Store code for benchmarks which does not check nor instrument the code.
#[cfg(feature = "runtime-benchmarks")]
pub fn store_code_raw(code: Vec<u8>) -> DispatchResult {
let schedule = <Module<T>>::current_schedule();
PrefabWasmModule::store_code_unchecked(code, &schedule)?;
Ok(())
}
/// Determine the address of a contract, /// Determine the address of a contract,
/// ///
/// This is the address generation function used by contract instantiation. Its result /// This is the address generation function used by contract instantiation. Its result
@@ -775,23 +780,22 @@ where
pub fn subsistence_threshold() -> BalanceOf<T> { pub fn subsistence_threshold() -> BalanceOf<T> {
T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get()) T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get())
} }
}
impl<T: Config> Module<T> /// Store code for benchmarks which does not check nor instrument the code.
where #[cfg(feature = "runtime-benchmarks")]
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>, fn store_code_raw(code: Vec<u8>) -> DispatchResult {
{
fn execute_wasm(
origin: T::AccountId,
gas_meter: &mut GasMeter<T>,
func: impl FnOnce(
&mut ExecutionContext<T, PrefabWasmModule<T>>,
&mut GasMeter<T>,
) -> ExecResult,
) -> ExecResult {
let schedule = <Module<T>>::current_schedule(); let schedule = <Module<T>>::current_schedule();
let mut ctx = ExecutionContext::top_level(origin, &schedule); PrefabWasmModule::store_code_unchecked(code, &schedule)?;
func(&mut ctx, gas_meter) Ok(())
}
/// This exists so that benchmarks can determine the weight of running an instrumentation.
#[cfg(feature = "runtime-benchmarks")]
fn reinstrument_module(
module: &mut PrefabWasmModule<T>,
schedule: &Schedule<T>
) -> DispatchResult {
self::wasm::reinstrument(module, schedule)
} }
} }
+17 -12
View File
@@ -325,13 +325,14 @@ where
pub fn try_eviction( pub fn try_eviction(
account: &T::AccountId, account: &T::AccountId,
handicap: T::BlockNumber, handicap: T::BlockNumber,
) -> Result<Option<BalanceOf<T>>, DispatchError> { ) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {
let contract = <ContractInfoOf<T>>::get(account); let contract = <ContractInfoOf<T>>::get(account);
let contract = match contract { let contract = match contract {
None | Some(ContractInfo::Tombstone(_)) => return Ok(None), None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),
Some(ContractInfo::Alive(contract)) => contract, Some(ContractInfo::Alive(contract)) => contract,
}; };
let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?; let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
let code_len = module.code_len();
let current_block_number = <frame_system::Module<T>>::block_number(); let current_block_number = <frame_system::Module<T>>::block_number();
let verdict = Self::consider_case( let verdict = Self::consider_case(
account, account,
@@ -353,9 +354,9 @@ where
Self::enact_verdict( Self::enact_verdict(
account, contract, current_block_number, verdict, Some(module), account, contract, current_block_number, verdict, Some(module),
)?; )?;
Ok(Some(rent_payed)) Ok((Some(rent_payed), code_len))
} }
_ => Ok(None), _ => Ok((None, code_len)),
} }
} }
@@ -447,28 +448,32 @@ where
/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to /// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
/// the restored account. The restored account will inherit the last write block and its last /// the restored account. The restored account will inherit the last write block and its last
/// deduct block will be set to the current block. /// deduct block will be set to the current block.
///
/// # Return Value
///
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
pub fn restore_to( pub fn restore_to(
origin: T::AccountId, origin: T::AccountId,
dest: T::AccountId, dest: T::AccountId,
code_hash: CodeHash<T>, code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>, rent_allowance: BalanceOf<T>,
delta: Vec<crate::exec::StorageKey>, delta: Vec<crate::exec::StorageKey>,
) -> Result<(), DispatchError> { ) -> Result<(u32, u32), (DispatchError, u32, u32)> {
let mut origin_contract = <ContractInfoOf<T>>::get(&origin) let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
.and_then(|c| c.get_alive()) .and_then(|c| c.get_alive())
.ok_or(Error::<T>::InvalidSourceContract)?; .ok_or((Error::<T>::InvalidSourceContract.into(), 0, 0))?;
let child_trie_info = origin_contract.child_trie_info(); let child_trie_info = origin_contract.child_trie_info();
let current_block = <frame_system::Module<T>>::block_number(); let current_block = <frame_system::Module<T>>::block_number();
if origin_contract.last_write == Some(current_block) { if origin_contract.last_write == Some(current_block) {
return Err(Error::<T>::InvalidContractOrigin.into()); return Err((Error::<T>::InvalidContractOrigin.into(), 0, 0));
} }
let dest_tombstone = <ContractInfoOf<T>>::get(&dest) let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
.and_then(|c| c.get_tombstone()) .and_then(|c| c.get_tombstone())
.ok_or(Error::<T>::InvalidDestinationContract)?; .ok_or((Error::<T>::InvalidDestinationContract.into(), 0, 0))?;
let last_write = if !delta.is_empty() { let last_write = if !delta.is_empty() {
Some(current_block) Some(current_block)
@@ -477,7 +482,7 @@ where
}; };
// Fails if the code hash does not exist on chain // Fails if the code hash does not exist on chain
E::add_user(code_hash)?; let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?;
// We are allowed to eagerly modify storage even though the function can // We are allowed to eagerly modify storage even though the function can
// fail later due to tombstones not matching. This is because the restoration // fail later due to tombstones not matching. This is because the restoration
@@ -501,13 +506,13 @@ where
); );
if tombstone != dest_tombstone { if tombstone != dest_tombstone {
return Err(Error::<T>::InvalidTombstone.into()); return Err((Error::<T>::InvalidTombstone.into(), caller_code_len, 0));
} }
origin_contract.storage_size -= bytes_taken; origin_contract.storage_size -= bytes_taken;
<ContractInfoOf<T>>::remove(&origin); <ContractInfoOf<T>>::remove(&origin);
E::remove_user(origin_contract.code_hash); let tombstone_code_len = E::remove_user(origin_contract.code_hash);
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> { <ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
trie_id: origin_contract.trie_id, trie_id: origin_contract.trie_id,
storage_size: origin_contract.storage_size, storage_size: origin_contract.storage_size,
@@ -523,6 +528,6 @@ where
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero()); T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
T::Currency::deposit_creating(&dest, origin_free_balance); T::Currency::deposit_creating(&dest, origin_free_balance);
Ok(()) Ok((caller_code_len, tombstone_code_len))
} }
} }
+27 -12
View File
@@ -104,10 +104,6 @@ pub struct Limits {
/// The maximum length of a subject in bytes used for PRNG generation. /// The maximum length of a subject in bytes used for PRNG generation.
pub subject_len: u32, pub subject_len: u32,
/// The maximum length of a contract code in bytes. This limit applies to the uninstrumented
/// and pristine form of the code as supplied to `instantiate_with_code`.
pub code_size: u32,
} }
impl Limits { impl Limits {
@@ -250,9 +246,18 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_terminate`. /// Weight of calling `seal_terminate`.
pub terminate: Weight, pub terminate: Weight,
/// Weight per byte of the terminated contract.
pub terminate_per_code_byte: Weight,
/// Weight of calling `seal_restore_to`. /// Weight of calling `seal_restore_to`.
pub restore_to: Weight, pub restore_to: Weight,
/// Weight per byte of the restoring contract.
pub restore_to_per_caller_code_byte: Weight,
/// Weight per byte of the restored contract.
pub restore_to_per_tombstone_code_byte: Weight,
/// Weight per delta key supplied to `seal_restore_to`. /// Weight per delta key supplied to `seal_restore_to`.
pub restore_to_per_delta: Weight, pub restore_to_per_delta: Weight,
@@ -292,6 +297,9 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_call`. /// Weight of calling `seal_call`.
pub call: Weight, pub call: Weight,
/// Weight per byte of the called contract.
pub call_per_code_byte: Weight,
/// Weight surcharge that is claimed if `seal_call` does a balance transfer. /// Weight surcharge that is claimed if `seal_call` does a balance transfer.
pub call_transfer_surcharge: Weight, pub call_transfer_surcharge: Weight,
@@ -304,6 +312,9 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_instantiate`. /// Weight of calling `seal_instantiate`.
pub instantiate: Weight, pub instantiate: Weight,
/// Weight per byte of the instantiated contract.
pub instantiate_per_code_byte: Weight,
/// Weight per input byte supplied to `seal_instantiate`. /// Weight per input byte supplied to `seal_instantiate`.
pub instantiate_per_input_byte: Weight, pub instantiate_per_input_byte: Weight,
@@ -443,7 +454,6 @@ impl Default for Limits {
table_size: 4096, table_size: 4096,
br_table_size: 256, br_table_size: 256,
subject_len: 32, subject_len: 32,
code_size: 512 * 1024,
} }
} }
} }
@@ -528,8 +538,11 @@ impl<T: Config> Default for HostFnWeights<T> {
r#return: cost!(seal_return), r#return: cost!(seal_return),
return_per_byte: cost_byte!(seal_return_per_kb), return_per_byte: cost_byte!(seal_return_per_kb),
terminate: cost!(seal_terminate), terminate: cost!(seal_terminate),
terminate_per_code_byte: cost_byte!(seal_terminate_per_code_kb),
restore_to: cost!(seal_restore_to), restore_to: cost!(seal_restore_to),
restore_to_per_delta: cost_batched!(seal_restore_to_per_delta), restore_to_per_caller_code_byte: cost_byte_args!(seal_restore_to_per_code_kb_delta, 1, 0, 0),
restore_to_per_tombstone_code_byte: cost_byte_args!(seal_restore_to_per_code_kb_delta, 0, 1, 0),
restore_to_per_delta: cost_batched_args!(seal_restore_to_per_code_kb_delta, 0, 0, 1),
random: cost_batched!(seal_random), random: cost_batched!(seal_random),
deposit_event: cost_batched!(seal_deposit_event), deposit_event: cost_batched!(seal_deposit_event),
deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0), deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0),
@@ -542,13 +555,15 @@ impl<T: Config> Default for HostFnWeights<T> {
get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb), get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb),
transfer: cost_batched!(seal_transfer), transfer: cost_batched!(seal_transfer),
call: cost_batched!(seal_call), call: cost_batched!(seal_call),
call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0), call_per_code_byte: cost_byte_batched_args!(seal_call_per_code_transfer_input_output_kb, 1, 0, 0, 0),
call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0), call_transfer_surcharge: cost_batched_args!(seal_call_per_code_transfer_input_output_kb, 0, 1, 0, 0),
call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1), call_per_input_byte: cost_byte_batched_args!(seal_call_per_code_transfer_input_output_kb, 0, 0, 1, 0),
call_per_output_byte: cost_byte_batched_args!(seal_call_per_code_transfer_input_output_kb, 0, 0, 0, 1),
instantiate: cost_batched!(seal_instantiate), instantiate: cost_batched!(seal_instantiate),
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 1, 0, 0), instantiate_per_code_byte: cost_byte_batched_args!(seal_instantiate_per_code_input_output_salt_kb, 1, 0, 0, 0),
instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 1, 0), instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_code_input_output_salt_kb, 0, 1, 0, 0),
instantiate_per_salt_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 0, 1), instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_code_input_output_salt_kb, 0, 0, 1, 0),
instantiate_per_salt_byte: cost_byte_batched_args!(seal_instantiate_per_code_input_output_salt_kb, 0, 0, 0, 1),
hash_sha2_256: cost_batched!(seal_hash_sha2_256), hash_sha2_256: cost_batched!(seal_hash_sha2_256),
hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb), hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb),
hash_keccak_256: cost_batched!(seal_hash_keccak_256), hash_keccak_256: cost_batched!(seal_hash_keccak_256),
+72 -3
View File
@@ -24,6 +24,7 @@ use crate::{
UncheckedFrom, InitState, ReturnFlags, UncheckedFrom, InitState, ReturnFlags,
}, },
exec::{AccountIdOf, Executable}, wasm::PrefabWasmModule, exec::{AccountIdOf, Executable}, wasm::PrefabWasmModule,
weights::WeightInfo,
}; };
use assert_matches::assert_matches; use assert_matches::assert_matches;
use codec::Encode; use codec::Encode;
@@ -35,7 +36,7 @@ use sp_runtime::{
use sp_io::hashing::blake2_256; use sp_io::hashing::blake2_256;
use frame_support::{ use frame_support::{
assert_ok, assert_err, assert_err_ignore_postinfo, assert_ok, assert_err, assert_err_ignore_postinfo,
parameter_types, StorageMap, assert_storage_noop, parameter_types, StorageMap, StorageValue, assert_storage_noop,
traits::{Currency, ReservableCurrency, OnInitialize}, traits::{Currency, ReservableCurrency, OnInitialize},
weights::{Weight, PostDispatchInfo, DispatchClass, constants::WEIGHT_PER_SECOND}, weights::{Weight, PostDispatchInfo, DispatchClass, constants::WEIGHT_PER_SECOND},
dispatch::DispatchErrorWithPostInfo, dispatch::DispatchErrorWithPostInfo,
@@ -250,6 +251,7 @@ parameter_types! {
pub const MaxValueSize: u32 = 16_384; pub const MaxValueSize: u32 = 16_384;
pub const DeletionQueueDepth: u32 = 1024; pub const DeletionQueueDepth: u32 = 1024;
pub const DeletionWeightLimit: Weight = 500_000_000_000; pub const DeletionWeightLimit: Weight = 500_000_000_000;
pub const MaxCodeSize: u32 = 2 * 1024;
} }
parameter_types! { parameter_types! {
@@ -282,6 +284,7 @@ impl Config for Test {
type ChainExtension = TestExtension; type ChainExtension = TestExtension;
type DeletionQueueDepth = DeletionQueueDepth; type DeletionQueueDepth = DeletionQueueDepth;
type DeletionWeightLimit = DeletionWeightLimit; type DeletionWeightLimit = DeletionWeightLimit;
type MaxCodeSize = MaxCodeSize;
} }
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
@@ -350,7 +353,7 @@ where
fn calling_plain_account_fails() { fn calling_plain_account_fails() {
ExtBuilder::default().build().execute_with(|| { ExtBuilder::default().build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 100_000_000); let _ = Balances::deposit_creating(&ALICE, 100_000_000);
let base_cost = <<Test as crate::Config>::WeightInfo as crate::WeightInfo>::call(); let base_cost = <<Test as Config>::WeightInfo as WeightInfo>::call(0);
assert_eq!( assert_eq!(
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()), Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()),
@@ -2432,7 +2435,7 @@ fn lazy_removal_does_no_run_on_full_block() {
// Run the lazy removal without any limit so that all keys would be removed if there // Run the lazy removal without any limit so that all keys would be removed if there
// had been some weight left in the block. // had been some weight left in the block.
let weight_used = Contracts::on_initialize(Weight::max_value()); let weight_used = Contracts::on_initialize(Weight::max_value());
let base = <<Test as crate::Config>::WeightInfo as crate::WeightInfo>::on_initialize(); let base = <<Test as Config>::WeightInfo as WeightInfo>::on_initialize();
assert_eq!(weight_used, base); assert_eq!(weight_used, base);
// All the keys are still in place // All the keys are still in place
@@ -2717,3 +2720,69 @@ fn refcounter() {
assert_matches!(crate::CodeStorage::<Test>::get(code_hash), None); assert_matches!(crate::CodeStorage::<Test>::get(code_hash), None);
}); });
} }
#[test]
fn reinstrument_does_charge() {
let (wasm, code_hash) = compile_module::<Test>("return_with_data").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let subsistence = Module::<Test>::subsistence_threshold();
let zero = 0u32.to_le_bytes().encode();
let code_len = wasm.len() as u32;
assert_ok!(Contracts::instantiate_with_code(
Origin::signed(ALICE),
subsistence * 100,
GAS_LIMIT,
wasm,
zero.clone(),
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
// Call the contract two times without reinstrument
let result0 = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
zero.clone(),
);
assert!(result0.exec_result.unwrap().is_success());
let result1 = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
zero.clone(),
);
assert!(result1.exec_result.unwrap().is_success());
// They should match because both where called with the same schedule.
assert_eq!(result0.gas_consumed, result1.gas_consumed);
// Update the schedule version but keep the rest the same
crate::CurrentSchedule::mutate(|old: &mut Schedule<Test>| {
old.version += 1;
});
// This call should trigger reinstrumentation
let result2 = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
zero.clone(),
);
assert!(result2.exec_result.unwrap().is_success());
assert!(result2.gas_consumed > result1.gas_consumed);
assert_eq!(
result2.gas_consumed,
result1.gas_consumed + <Test as Config>::WeightInfo::instrument(code_len / 1024),
);
});
}
@@ -30,9 +30,13 @@
use crate::{ use crate::{
CodeHash, CodeStorage, PristineCode, Schedule, Config, Error, CodeHash, CodeStorage, PristineCode, Schedule, Config, Error,
wasm::{prepare, PrefabWasmModule}, Module as Contracts, RawEvent, wasm::{prepare, PrefabWasmModule}, Module as Contracts, RawEvent,
gas::{Gas, GasMeter, Token},
weights::WeightInfo,
}; };
use sp_core::crypto::UncheckedFrom; use sp_core::crypto::UncheckedFrom;
use frame_support::{StorageMap, dispatch::{DispatchError, DispatchResult}}; use frame_support::{StorageMap, dispatch::DispatchError};
#[cfg(feature = "runtime-benchmarks")]
pub use self::private::reinstrument as reinstrument;
/// Put the instrumented module in storage. /// Put the instrumented module in storage.
/// ///
@@ -77,14 +81,14 @@ where
} }
/// Increment the refcount of a code in-storage by one. /// Increment the refcount of a code in-storage by one.
pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>) -> DispatchResult pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>) -> Result<u32, DispatchError>
where where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{ {
<CodeStorage<T>>::mutate(code_hash, |existing| { <CodeStorage<T>>::mutate(code_hash, |existing| {
if let Some(module) = existing { if let Some(module) = existing {
increment_64(&mut module.refcount); increment_64(&mut module.refcount);
Ok(()) Ok(module.original_code_len)
} else { } else {
Err(Error::<T>::CodeNotFound.into()) Err(Error::<T>::CodeNotFound.into())
} }
@@ -92,19 +96,23 @@ where
} }
/// Decrement the refcount of a code in-storage by one and remove the code when it drops to zero. /// Decrement the refcount of a code in-storage by one and remove the code when it drops to zero.
pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>) pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>) -> u32
where where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{ {
<CodeStorage<T>>::mutate_exists(code_hash, |existing| { <CodeStorage<T>>::mutate_exists(code_hash, |existing| {
if let Some(module) = existing { if let Some(module) = existing {
let code_len = module.original_code_len;
module.refcount = module.refcount.saturating_sub(1); module.refcount = module.refcount.saturating_sub(1);
if module.refcount == 0 { if module.refcount == 0 {
*existing = None; *existing = None;
finish_removal::<T>(code_hash); finish_removal::<T>(code_hash);
} }
code_len
} else {
0
} }
}); })
} }
/// Load code with the given code hash. /// Load code with the given code hash.
@@ -114,31 +122,48 @@ where
/// re-instrumentation and update the cache in the storage. /// re-instrumentation and update the cache in the storage.
pub fn load<T: Config>( pub fn load<T: Config>(
code_hash: CodeHash<T>, code_hash: CodeHash<T>,
schedule: Option<&Schedule<T>>, reinstrument: Option<(&Schedule<T>, &mut GasMeter<T>)>,
) -> Result<PrefabWasmModule<T>, DispatchError> ) -> Result<PrefabWasmModule<T>, DispatchError>
where where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{ {
let mut prefab_module = <CodeStorage<T>>::get(code_hash) let mut prefab_module = <CodeStorage<T>>::get(code_hash)
.ok_or_else(|| Error::<T>::CodeNotFound)?; .ok_or_else(|| Error::<T>::CodeNotFound)?;
prefab_module.code_hash = code_hash;
if let Some(schedule) = schedule { if let Some((schedule, gas_meter)) = reinstrument {
if prefab_module.schedule_version < schedule.version { if prefab_module.schedule_version < schedule.version {
// The current schedule version is greater than the version of the one cached // The current schedule version is greater than the version of the one cached
// in the storage. // in the storage.
// //
// We need to re-instrument the code with the latest schedule here. // We need to re-instrument the code with the latest schedule here.
let original_code = <PristineCode<T>>::get(code_hash) gas_meter.charge(&(), InstrumentToken(prefab_module.original_code_len))?;
.ok_or_else(|| Error::<T>::CodeNotFound)?; private::reinstrument(&mut prefab_module, schedule)?;
prefab_module.code = prepare::reinstrument_contract::<T>(original_code, schedule)?;
prefab_module.schedule_version = schedule.version;
<CodeStorage<T>>::insert(&code_hash, &prefab_module);
} }
} }
prefab_module.code_hash = code_hash;
Ok(prefab_module) Ok(prefab_module)
} }
mod private {
use super::*;
/// Instruments the passed prefab wasm module with the supplied schedule.
pub fn reinstrument<T: Config>(
prefab_module: &mut PrefabWasmModule<T>,
schedule: &Schedule<T>,
) -> Result<(), DispatchError>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
let original_code = <PristineCode<T>>::get(&prefab_module.code_hash)
.ok_or_else(|| Error::<T>::CodeNotFound)?;
prefab_module.code = prepare::reinstrument_contract::<T>(original_code, schedule)?;
prefab_module.schedule_version = schedule.version;
<CodeStorage<T>>::insert(&prefab_module.code_hash, &*prefab_module);
Ok(())
}
}
/// Finish removal of a code by deleting the pristine code and emitting an event. /// Finish removal of a code by deleting the pristine code and emitting an event.
fn finish_removal<T: Config>(code_hash: CodeHash<T>) fn finish_removal<T: Config>(code_hash: CodeHash<T>)
where where
@@ -161,3 +186,17 @@ fn increment_64(refcount: &mut u64) {
qed qed
"); ");
} }
/// Token to be supplied to the gas meter which charges the weight needed for reinstrumenting
/// a contract of the specified size in bytes.
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Clone, Copy)]
struct InstrumentToken(u32);
impl<T: Config> Token<T> for InstrumentToken {
type Metadata = ();
fn calculate_amount(&self, _metadata: &Self::Metadata) -> Gas {
T::WeightInfo::instrument(self.0 / 1024)
}
}
+25 -17
View File
@@ -33,9 +33,11 @@ use crate::{
use sp_std::prelude::*; use sp_std::prelude::*;
use sp_core::crypto::UncheckedFrom; use sp_core::crypto::UncheckedFrom;
use codec::{Encode, Decode}; use codec::{Encode, Decode};
use frame_support::dispatch::{DispatchError, DispatchResult}; use frame_support::dispatch::DispatchError;
use pallet_contracts_primitives::ExecResult; use pallet_contracts_primitives::ExecResult;
pub use self::runtime::{ReturnCode, Runtime, RuntimeToken}; pub use self::runtime::{ReturnCode, Runtime, RuntimeToken};
#[cfg(feature = "runtime-benchmarks")]
pub use self::code_cache::reinstrument;
/// A prepared wasm module ready for execution. /// A prepared wasm module ready for execution.
/// ///
@@ -125,7 +127,7 @@ where
pub fn store_code_unchecked( pub fn store_code_unchecked(
original_code: Vec<u8>, original_code: Vec<u8>,
schedule: &Schedule<T> schedule: &Schedule<T>
) -> DispatchResult { ) -> Result<(), DispatchError> {
let executable = prepare::benchmarking::prepare_contract(original_code, schedule) let executable = prepare::benchmarking::prepare_contract(original_code, schedule)
.map_err::<DispatchError, _>(Into::into)?; .map_err::<DispatchError, _>(Into::into)?;
code_cache::store(executable); code_cache::store(executable);
@@ -145,9 +147,10 @@ where
{ {
fn from_storage( fn from_storage(
code_hash: CodeHash<T>, code_hash: CodeHash<T>,
schedule: &Schedule<T> schedule: &Schedule<T>,
gas_meter: &mut GasMeter<T>,
) -> Result<Self, DispatchError> { ) -> Result<Self, DispatchError> {
code_cache::load(code_hash, Some(schedule)) code_cache::load(code_hash, Some((schedule, gas_meter)))
} }
fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError> { fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError> {
@@ -158,11 +161,11 @@ where
code_cache::store_decremented(self); code_cache::store_decremented(self);
} }
fn add_user(code_hash: CodeHash<T>) -> DispatchResult { fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError> {
code_cache::increment_refcount::<T>(code_hash) code_cache::increment_refcount::<T>(code_hash)
} }
fn remove_user(code_hash: CodeHash<T>) { fn remove_user(code_hash: CodeHash<T>) -> u32 {
code_cache::decrement_refcount::<T>(code_hash) code_cache::decrement_refcount::<T>(code_hash)
} }
@@ -222,6 +225,10 @@ where
let len = self.original_code_len.saturating_add(self.code.len() as u32); let len = self.original_code_len.saturating_add(self.code.len() as u32);
len.checked_div(self.refcount as u32).unwrap_or(len) len.checked_div(self.refcount as u32).unwrap_or(len)
} }
fn code_len(&self) -> u32 {
self.code.len() as u32
}
} }
#[cfg(test)] #[cfg(test)]
@@ -305,7 +312,7 @@ mod tests {
gas_meter: &mut GasMeter<Test>, gas_meter: &mut GasMeter<Test>,
data: Vec<u8>, data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError> { ) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)> {
self.instantiates.push(InstantiateEntry { self.instantiates.push(InstantiateEntry {
code_hash: code_hash.clone(), code_hash: code_hash.clone(),
endowment, endowment,
@@ -319,6 +326,7 @@ mod tests {
flags: ReturnFlags::empty(), flags: ReturnFlags::empty(),
data: Vec::new(), data: Vec::new(),
}, },
0,
)) ))
} }
fn transfer( fn transfer(
@@ -339,7 +347,7 @@ mod tests {
value: u64, value: u64,
_gas_meter: &mut GasMeter<Test>, _gas_meter: &mut GasMeter<Test>,
data: Vec<u8>, data: Vec<u8>,
) -> ExecResult { ) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
self.transfers.push(TransferEntry { self.transfers.push(TransferEntry {
to: to.clone(), to: to.clone(),
value, value,
@@ -347,16 +355,16 @@ mod tests {
}); });
// Assume for now that it was just a plain transfer. // Assume for now that it was just a plain transfer.
// TODO: Add tests for different call outcomes. // TODO: Add tests for different call outcomes.
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }) Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }, 0))
} }
fn terminate( fn terminate(
&mut self, &mut self,
beneficiary: &AccountIdOf<Self::T>, beneficiary: &AccountIdOf<Self::T>,
) -> Result<(), DispatchError> { ) -> Result<u32, (DispatchError, u32)> {
self.terminations.push(TerminationEntry { self.terminations.push(TerminationEntry {
beneficiary: beneficiary.clone(), beneficiary: beneficiary.clone(),
}); });
Ok(()) Ok(0)
} }
fn restore_to( fn restore_to(
&mut self, &mut self,
@@ -364,14 +372,14 @@ mod tests {
code_hash: H256, code_hash: H256,
rent_allowance: u64, rent_allowance: u64,
delta: Vec<StorageKey>, delta: Vec<StorageKey>,
) -> Result<(), DispatchError> { ) -> Result<(u32, u32), (DispatchError, u32, u32)> {
self.restores.push(RestoreEntry { self.restores.push(RestoreEntry {
dest, dest,
code_hash, code_hash,
rent_allowance, rent_allowance,
delta, delta,
}); });
Ok(()) Ok((0, 0))
} }
fn caller(&self) -> &AccountIdOf<Self::T> { fn caller(&self) -> &AccountIdOf<Self::T> {
&ALICE &ALICE
@@ -443,7 +451,7 @@ mod tests {
gas_meter: &mut GasMeter<Test>, gas_meter: &mut GasMeter<Test>,
input_data: Vec<u8>, input_data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError> { ) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)> {
(**self).instantiate(code, value, gas_meter, input_data, salt) (**self).instantiate(code, value, gas_meter, input_data, salt)
} }
fn transfer( fn transfer(
@@ -456,7 +464,7 @@ mod tests {
fn terminate( fn terminate(
&mut self, &mut self,
beneficiary: &AccountIdOf<Self::T>, beneficiary: &AccountIdOf<Self::T>,
) -> Result<(), DispatchError> { ) -> Result<u32, (DispatchError, u32)> {
(**self).terminate(beneficiary) (**self).terminate(beneficiary)
} }
fn call( fn call(
@@ -465,7 +473,7 @@ mod tests {
value: u64, value: u64,
gas_meter: &mut GasMeter<Test>, gas_meter: &mut GasMeter<Test>,
input_data: Vec<u8>, input_data: Vec<u8>,
) -> ExecResult { ) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
(**self).call(to, value, gas_meter, input_data) (**self).call(to, value, gas_meter, input_data)
} }
fn restore_to( fn restore_to(
@@ -474,7 +482,7 @@ mod tests {
code_hash: H256, code_hash: H256,
rent_allowance: u64, rent_allowance: u64,
delta: Vec<StorageKey>, delta: Vec<StorageKey>,
) -> Result<(), DispatchError> { ) -> Result<(u32, u32), (DispatchError, u32, u32)> {
(**self).restore_to( (**self).restore_to(
dest, dest,
code_hash, code_hash,
+80 -18
View File
@@ -20,11 +20,11 @@
use crate::{ use crate::{
HostFnWeights, Config, CodeHash, BalanceOf, Error, HostFnWeights, Config, CodeHash, BalanceOf, Error,
exec::{Ext, StorageKey, TopicOf}, exec::{Ext, StorageKey, TopicOf},
gas::{Gas, GasMeter, Token, GasMeterResult, ChargedAmount}, gas::{Gas, GasMeter, Token, ChargedAmount},
wasm::env_def::ConvertibleToWasm, wasm::env_def::ConvertibleToWasm,
}; };
use parity_wasm::elements::ValueType; use parity_wasm::elements::ValueType;
use frame_support::{dispatch::DispatchError, ensure}; use frame_support::{dispatch::DispatchError, ensure, traits::Get};
use sp_std::prelude::*; use sp_std::prelude::*;
use codec::{Decode, DecodeAll, Encode}; use codec::{Decode, DecodeAll, Encode};
use sp_runtime::traits::SaturatedConversion; use sp_runtime::traits::SaturatedConversion;
@@ -165,8 +165,12 @@ pub enum RuntimeToken {
Return(u32), Return(u32),
/// Weight of calling `seal_terminate`. /// Weight of calling `seal_terminate`.
Terminate, Terminate,
/// Weight that is added to `seal_terminate` for every byte of the terminated contract.
TerminateSurchargeCodeSize(u32),
/// Weight of calling `seal_restore_to` per number of supplied delta entries. /// Weight of calling `seal_restore_to` per number of supplied delta entries.
RestoreTo(u32), RestoreTo(u32),
/// Weight that is added to `seal_restore_to` for the involved code sizes.
RestoreToSurchargeCodeSize{caller_code: u32, tombstone_code: u32},
/// Weight of calling `seal_random`. It includes the weight for copying the subject. /// Weight of calling `seal_random`. It includes the weight for copying the subject.
Random, Random,
/// Weight of calling `seal_reposit_event` with the given number of topics and event size. /// Weight of calling `seal_reposit_event` with the given number of topics and event size.
@@ -185,6 +189,8 @@ pub enum RuntimeToken {
Transfer, Transfer,
/// Weight of calling `seal_call` for the given input size. /// Weight of calling `seal_call` for the given input size.
CallBase(u32), CallBase(u32),
/// Weight that is added to `seal_call` for every byte of the called contract.
CallSurchargeCodeSize(u32),
/// Weight of the transfer performed during a call. /// Weight of the transfer performed during a call.
CallSurchargeTransfer, CallSurchargeTransfer,
/// Weight of output received through `seal_call` for the given size. /// Weight of output received through `seal_call` for the given size.
@@ -193,6 +199,8 @@ pub enum RuntimeToken {
/// This includes the transfer as an instantiate without a value will always be below /// This includes the transfer as an instantiate without a value will always be below
/// the existential deposit and is disregarded as corner case. /// the existential deposit and is disregarded as corner case.
InstantiateBase{input_data_len: u32, salt_len: u32}, InstantiateBase{input_data_len: u32, salt_len: u32},
/// Weight that is added to `seal_instantiate` for every byte of the instantiated contract.
InstantiateSurchargeCodeSize(u32),
/// Weight of output received through `seal_instantiate` for the given size. /// Weight of output received through `seal_instantiate` for the given size.
InstantiateCopyOut(u32), InstantiateCopyOut(u32),
/// Weight of calling `seal_hash_sha_256` for the given input size. /// Weight of calling `seal_hash_sha_256` for the given input size.
@@ -235,8 +243,13 @@ where
Return(len) => s.r#return Return(len) => s.r#return
.saturating_add(s.return_per_byte.saturating_mul(len.into())), .saturating_add(s.return_per_byte.saturating_mul(len.into())),
Terminate => s.terminate, Terminate => s.terminate,
TerminateSurchargeCodeSize(len) => s.terminate_per_code_byte.saturating_mul(len.into()),
RestoreTo(delta) => s.restore_to RestoreTo(delta) => s.restore_to
.saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())), .saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())),
RestoreToSurchargeCodeSize{caller_code, tombstone_code} =>
s.restore_to_per_caller_code_byte.saturating_mul(caller_code.into()).saturating_add(
s.restore_to_per_tombstone_code_byte.saturating_mul(tombstone_code.into())
),
Random => s.random, Random => s.random,
DepositEvent{num_topic, len} => s.deposit_event DepositEvent{num_topic, len} => s.deposit_event
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into())) .saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
@@ -250,11 +263,14 @@ where
Transfer => s.transfer, Transfer => s.transfer,
CallBase(len) => s.call CallBase(len) => s.call
.saturating_add(s.call_per_input_byte.saturating_mul(len.into())), .saturating_add(s.call_per_input_byte.saturating_mul(len.into())),
CallSurchargeCodeSize(len) => s.call_per_code_byte.saturating_mul(len.into()),
CallSurchargeTransfer => s.call_transfer_surcharge, CallSurchargeTransfer => s.call_transfer_surcharge,
CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()), CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()),
InstantiateBase{input_data_len, salt_len} => s.instantiate InstantiateBase{input_data_len, salt_len} => s.instantiate
.saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into())) .saturating_add(s.instantiate_per_input_byte.saturating_mul(input_data_len.into()))
.saturating_add(s.instantiate_per_salt_byte.saturating_mul(salt_len.into())), .saturating_add(s.instantiate_per_salt_byte.saturating_mul(salt_len.into())),
InstantiateSurchargeCodeSize(len) =>
s.instantiate_per_code_byte.saturating_mul(len.into()),
InstantiateCopyOut(len) => s.instantiate_per_output_byte InstantiateCopyOut(len) => s.instantiate_per_output_byte
.saturating_mul(len.into()), .saturating_mul(len.into()),
HashSha256(len) => s.hash_sha2_256 HashSha256(len) => s.hash_sha2_256
@@ -408,10 +424,19 @@ where
where where
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>, Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
{ {
match self.gas_meter.charge(&self.ext.schedule().host_fn_weights, token) { self.gas_meter.charge(&self.ext.schedule().host_fn_weights, token)
GasMeterResult::Proceed(amount) => Ok(amount), }
GasMeterResult::OutOfGas => Err(Error::<E::T>::OutOfGas.into())
} /// 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(
charged_amount,
&self.ext.schedule().host_fn_weights,
adjusted_amount,
);
} }
/// Read designated chunk from the sandbox memory. /// Read designated chunk from the sandbox memory.
@@ -774,11 +799,12 @@ define_env!(Env, <E: Ext>,
ctx.read_sandbox_memory_as(callee_ptr, callee_len)?; 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 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 input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
if value > 0u32.into() { if value > 0u32.into() {
ctx.charge_gas(RuntimeToken::CallSurchargeTransfer)?; ctx.charge_gas(RuntimeToken::CallSurchargeTransfer)?;
} }
let charged = ctx.charge_gas(
RuntimeToken::CallSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
)?;
let nested_gas_limit = if gas == 0 { let nested_gas_limit = if gas == 0 {
ctx.gas_meter.gas_left() ctx.gas_meter.gas_left()
} else { } else {
@@ -796,16 +822,20 @@ define_env!(Env, <E: Ext>,
) )
} }
// there is not enough gas to allocate for the nested call. // there is not enough gas to allocate for the nested call.
None => Err(Error::<<E as Ext>::T>::OutOfGas.into()), None => Err((Error::<<E as Ext>::T>::OutOfGas.into(), 0)),
} }
}); });
let code_len = match &call_outcome {
if let Ok(output) = &call_outcome { Ok((_, len)) => len,
Err((_, len)) => len,
};
ctx.adjust_gas(charged, RuntimeToken::CallSurchargeCodeSize(*code_len));
if let Ok((output, _)) = &call_outcome {
ctx.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { ctx.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
Some(RuntimeToken::CallCopyOut(len)) Some(RuntimeToken::CallCopyOut(len))
})?; })?;
} }
Ok(Runtime::<E>::exec_into_return_code(call_outcome)?) Ok(Runtime::<E>::exec_into_return_code(call_outcome.map(|r| r.0).map_err(|r| r.0))?)
}, },
// Instantiate a contract with the specified code hash. // Instantiate a contract with the specified code hash.
@@ -875,7 +905,9 @@ define_env!(Env, <E: Ext>,
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr, value_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 input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
let salt = ctx.read_sandbox_memory(salt_ptr, salt_len)?; let salt = ctx.read_sandbox_memory(salt_ptr, salt_len)?;
let charged = ctx.charge_gas(
RuntimeToken::InstantiateSurchargeCodeSize(<E::T as Config>::MaxCodeSize::get())
)?;
let nested_gas_limit = if gas == 0 { let nested_gas_limit = if gas == 0 {
ctx.gas_meter.gas_left() ctx.gas_meter.gas_left()
} else { } else {
@@ -894,10 +926,15 @@ define_env!(Env, <E: Ext>,
) )
} }
// there is not enough gas to allocate for the nested call. // there is not enough gas to allocate for the nested call.
None => Err(Error::<<E as Ext>::T>::OutOfGas.into()), None => Err((Error::<<E as Ext>::T>::OutOfGas.into(), 0)),
} }
}); });
if let Ok((address, output)) = &instantiate_outcome { let code_len = match &instantiate_outcome {
Ok((_, _, code_len)) => code_len,
Err((_, code_len)) => code_len,
};
ctx.adjust_gas(charged, RuntimeToken::InstantiateSurchargeCodeSize(*code_len));
if let Ok((address, output, _)) = &instantiate_outcome {
if !output.flags.contains(ReturnFlags::REVERT) { if !output.flags.contains(ReturnFlags::REVERT) {
ctx.write_sandbox_output( ctx.write_sandbox_output(
address_ptr, address_len_ptr, &address.encode(), true, already_charged, address_ptr, address_len_ptr, &address.encode(), true, already_charged,
@@ -907,7 +944,9 @@ define_env!(Env, <E: Ext>,
Some(RuntimeToken::InstantiateCopyOut(len)) Some(RuntimeToken::InstantiateCopyOut(len))
})?; })?;
} }
Ok(Runtime::<E>::exec_into_return_code(instantiate_outcome.map(|(_id, retval)| retval))?) Ok(Runtime::<E>::exec_into_return_code(
instantiate_outcome.map(|(_, retval, _)| retval).map_err(|(err, _)| err)
)?)
}, },
// Remove the calling account and transfer remaining balance. // Remove the calling account and transfer remaining balance.
@@ -935,7 +974,15 @@ define_env!(Env, <E: Ext>,
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId = let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?; ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
ctx.ext.terminate(&beneficiary)?; let charged = ctx.charge_gas(
RuntimeToken::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));
result?;
Err(TrapReason::Termination) Err(TrapReason::Termination)
}, },
@@ -1220,7 +1267,22 @@ define_env!(Env, <E: Ext>,
delta delta
}; };
ctx.ext.restore_to(dest, code_hash, rent_allowance, delta)?; let max_len = <E::T as Config>::MaxCodeSize::get();
let charged = ctx.charge_gas(RuntimeToken::RestoreToSurchargeCodeSize {
caller_code: max_len,
tombstone_code: max_len,
})?;
let (result, caller_code, tombstone_code) = match ctx.ext.restore_to(
dest, code_hash, rent_allowance, delta
) {
Ok((code, tomb)) => (Ok(()), code, tomb),
Err((err, code, tomb)) => (Err(err), code, tomb),
};
ctx.adjust_gas(charged, RuntimeToken::RestoreToSurchargeCodeSize {
caller_code,
tombstone_code,
});
result?;
Err(TrapReason::Restoration) Err(TrapReason::Restoration)
}, },
File diff suppressed because it is too large Load Diff