contracts: Remove weight pre charging (#8976)

* Remove pre-charging for code size

* Remove pre charging when reading values of fixed size

* Add new versions of API functions that leave out parameters

* Update CHANGELOG.md

* Apply suggestions from code review

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Add v1 for seal_set_rent_allowance

* Remove unneeded trait bound

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

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
Alexander Theißen
2021-06-25 18:27:01 +02:00
committed by GitHub
parent bc0520913d
commit 0cccd282a1
18 changed files with 1238 additions and 1132 deletions
+5
View File
@@ -42,6 +42,11 @@ output to an RPC client.
- Make storage and fields of `Schedule` private to the crate. - Make storage and fields of `Schedule` private to the crate.
[#8359](https://github.com/paritytech/substrate/pull/8359) [#8359](https://github.com/paritytech/substrate/pull/8359)
### Fixed
- Remove pre-charging which caused wrongly estimated weights
[#8976](https://github.com/paritytech/substrate/pull/8976)
## [v3.0.0] 2021-02-25 ## [v3.0.0] 2021-02-25
This version constitutes the first release that brings any stability guarantees (see above). This version constitutes the first release that brings any stability guarantees (see above).
@@ -0,0 +1,5 @@
;; A valid contract which does nothing at all
(module
(func (export "deploy"))
(func (export "call"))
)
@@ -4,8 +4,8 @@
;; The rest of the input is forwarded to the constructor of the callee ;; The rest of the input is forwarded to the constructor of the callee
(module (module
(import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_instantiate" (func $seal_instantiate (import "seal1" "seal_instantiate" (func $seal_instantiate
(param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) (param i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
)) ))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1)) (import "env" "memory" (memory 1 1))
@@ -29,10 +29,8 @@
(i32.const 8) (i32.const 8)
(call $seal_instantiate (call $seal_instantiate
(i32.const 16) ;; Pointer to the code hash. (i32.const 16) ;; Pointer to the code hash.
(i32.const 32) ;; Length of the code hash.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all. (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 0) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 48) ;; Pointer to input data buffer address (i32.const 48) ;; Pointer to input data buffer address
(i32.const 4) ;; Length of input data buffer (i32.const 4) ;; Length of input data buffer
(i32.const 0xffffffff) ;; u32 max sentinel value: do not copy address (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy address
@@ -1,9 +1,9 @@
(module (module
(import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_restore_to" (import "seal1" "seal_restore_to"
(func $seal_restore_to (func $seal_restore_to
(param i32 i32 i32 i32 i32 i32 i32 i32) (param i32 i32 i32 i32 i32)
) )
) )
(import "env" "memory" (memory 1 1)) (import "env" "memory" (memory 1 1))
@@ -27,15 +27,12 @@
) )
) )
(call $seal_restore_to (call $seal_restore_to
;; Pointer and length of the encoded dest buffer. ;; Pointer to the encoded dest buffer.
(i32.const 340) (i32.const 340)
(i32.const 32) ;; Pointer to the encoded code hash buffer
;; Pointer and length of the encoded code hash buffer
(i32.const 308) (i32.const 308)
(i32.const 32) ;; Pointer to the encoded rent_allowance buffer
;; Pointer and length of the encoded rent_allowance buffer
(i32.const 296) (i32.const 296)
(i32.const 8)
;; Pointer and number of items in the delta buffer. ;; Pointer and number of items in the delta buffer.
;; This buffer specifies multiple keys for removal before restoration. ;; This buffer specifies multiple keys for removal before restoration.
(i32.const 100) (i32.const 100)
@@ -258,9 +258,14 @@ where
/// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size. /// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
pub fn dummy_with_bytes(dummy_bytes: u32) -> Self { pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
// We want the module to have the size `dummy_bytes`.
// This is not completely correct as the overhead grows when the contract grows
// because of variable length integer encoding. However, it is good enough to be that
// close for benchmarking purposes.
let module_overhead = 65;
ModuleDefinition { ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()), memory: Some(ImportedMemory::max::<T>()),
dummy_section: dummy_bytes, dummy_section: dummy_bytes.saturating_sub(module_overhead),
.. Default::default() .. Default::default()
} }
.into() .into()
@@ -320,6 +320,25 @@ benchmarks! {
Contracts::<T>::reinstrument_module(&mut module, &schedule)?; Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
} }
// The weight of loading and decoding of a contract's code per kilobyte.
code_load {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
Contracts::<T>::store_code_raw(code)?;
}: {
<PrefabWasmModule<T>>::from_storage_noinstr(hash)?;
}
// The weight of changing the refcount of a contract's code per kilobyte.
code_refcount {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
Contracts::<T>::store_code_raw(code)?;
let mut gas_meter = GasMeter::new(Weight::max_value());
}: {
<PrefabWasmModule<T>>::add_user(hash, &mut gas_meter)?;
}
// This constructs a contract that is maximal expensive to instrument. // This constructs a contract that is maximal expensive to instrument.
// It creates a maximum number of metering blocks per byte. // It creates a maximum number of metering blocks per byte.
// The size of the salt influences the runtime because is is hashed in order to // The size of the salt influences the runtime because is is hashed in order to
@@ -352,16 +371,14 @@ 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::Schedule::get().limits.code_len / 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_bytes(c * 1024); let WasmModule { code, hash, .. } = WasmModule::<T>::dummy();
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)?;
@@ -380,12 +397,10 @@ 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::Schedule::get().limits.code_len / 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_bytes(c * 1024), vec![], Endow::CollectRent whitelisted_caller(), WasmModule::dummy(), 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());
@@ -720,43 +735,6 @@ benchmarks! {
} }
} }
seal_terminate_per_code_kb {
let c in 0 .. T::Schedule::get().limits.code_len / 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 {
module: "seal0",
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;
@@ -836,18 +814,15 @@ benchmarks! {
} }
} }
// `c`: Code size of caller contract
// `t`: Code size of tombstone contract
// `d`: Number of supplied delta keys // `d`: Number of supplied delta keys
seal_restore_to_per_code_kb_delta { seal_restore_to_per_delta {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let t in 0 .. T::Schedule::get().limits.code_len / 1024;
let d in 0 .. API_BENCHMARK_BATCHES; let d in 0 .. API_BENCHMARK_BATCHES;
let mut tombstone = ContractWithStorage::<T>::with_code( let mut tombstone = ContractWithStorage::<T>::new(0, 0)?;
WasmModule::<T>::dummy_with_bytes(t * 1024), 0, 0
)?;
tombstone.evict()?; tombstone.evict()?;
let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::Schedule::get().limits.payload_len)?; let delta = create_storage::<T>(
d * API_BENCHMARK_BATCH_SIZE,
T::Schedule::get().limits.payload_len,
)?;
let dest = tombstone.contract.account_id.encode(); let dest = tombstone.contract.account_id.encode();
let dest_len = dest.len(); let dest_len = dest.len();
@@ -909,7 +884,6 @@ benchmarks! {
Instruction::Call(0), Instruction::Call(0),
Instruction::End, Instruction::End,
])), ])),
dummy_section: c * 1024,
.. Default::default() .. Default::default()
}); });
@@ -1393,8 +1367,7 @@ 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_code_transfer_input_output_kb { seal_call_per_transfer_input_output_kb {
let c in 0 .. T::Schedule::get().limits.code_len / 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;
@@ -1417,7 +1390,6 @@ 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)
@@ -1593,8 +1565,7 @@ benchmarks! {
} }
} }
seal_instantiate_per_code_input_output_salt_kb { seal_instantiate_per_input_output_salt_kb {
let c in 0 .. T::Schedule::get().limits.code_len / 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;
@@ -1617,7 +1588,6 @@ 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();
@@ -59,7 +59,7 @@ use crate::{
wasm::{Runtime, RuntimeCosts}, wasm::{Runtime, RuntimeCosts},
}; };
use codec::Decode; use codec::Decode;
use frame_support::weights::Weight; use frame_support::{weights::Weight, traits::MaxEncodedLen};
use sp_runtime::DispatchError; use sp_runtime::DispatchError;
use sp_std::{ use sp_std::{
marker::PhantomData, marker::PhantomData,
@@ -300,18 +300,21 @@ where
Ok(()) Ok(())
} }
/// Reads `in_len` from contract memory and scale decodes it. /// Reads and decodes a type with a size fixed at compile time from contract memory.
/// ///
/// This function is secure and recommended for all input types of fixed size /// This function is secure and recommended for all input types of fixed size
/// as long as the cost of reading the memory is included in the overall already charged /// as long as the cost of reading the memory is included in the overall already charged
/// weight of the chain extension. This should usually be the case when fixed input types /// weight of the chain extension. This should usually be the case when fixed input types
/// are used. Non fixed size types (like everything using `Vec`) usually need to use /// are used.
/// [`in_len()`](Self::in_len) in order to properly charge the necessary weight. pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
pub fn read_as<T: Decode>(&mut self) -> Result<T> { self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr)
self.inner.runtime.read_sandbox_memory_as( }
self.inner.input_ptr,
self.inner.input_len, /// Reads and decodes a type with a dynamic size from contract memory.
) ///
/// Make sure to include `len` in your weight calculations.
pub fn read_as_unbounded<T: Decode>(&mut self, len: u32) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as_unbounded(self.inner.input_ptr, len)
} }
/// The length of the input as passed in as `input_len`. /// The length of the input as passed in as `input_len`.
+90 -87
View File
@@ -168,7 +168,7 @@ pub trait Ext: sealing::Sealed {
value: BalanceOf<Self::T>, value: BalanceOf<Self::T>,
input_data: Vec<u8>, input_data: Vec<u8>,
allows_reentry: bool, allows_reentry: bool,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)>; ) -> Result<ExecReturnValue, ExecError>;
/// Instantiate a contract from the given code. /// Instantiate a contract from the given code.
/// ///
@@ -186,24 +186,16 @@ pub trait Ext: sealing::Sealed {
value: BalanceOf<Self::T>, value: BalanceOf<Self::T>,
input_data: Vec<u8>, input_data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)>; ) -> Result<(AccountIdOf<Self::T>, ExecReturnValue ), ExecError>;
/// 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.
/// fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError>;
/// # Return Value
///
/// Result<CodeSize, (DispatchError, CodeSize)>
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)>;
/// Restores the given destination contract sacrificing the current one. /// Restores the given destination contract sacrificing the current one.
/// ///
@@ -222,7 +214,7 @@ pub trait Ext: sealing::Sealed {
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>,
) -> Result<(u32, u32), (DispatchError, u32, u32)>; ) -> Result<(), DispatchError>;
/// Transfer some amount of funds into the specified account. /// Transfer some amount of funds into the specified account.
fn transfer( fn transfer(
@@ -325,6 +317,9 @@ 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.
///
/// # Note
/// Charges size base load and instrumentation weight from the gas meter.
fn from_storage( fn from_storage(
code_hash: CodeHash<T>, code_hash: CodeHash<T>,
schedule: &Schedule<T>, schedule: &Schedule<T>,
@@ -336,6 +331,10 @@ pub trait Executable<T: Config>: Sized {
/// A code module is re-instrumented on-load when it was originally instrumented with /// A code module is re-instrumented on-load when it was originally instrumented with
/// an older schedule. This skips this step for cases where the code storage is /// an older schedule. This skips this step for cases where the code storage is
/// queried for purposes other than execution. /// queried for purposes other than execution.
///
/// # Note
///
/// Does not charge from the gas meter. Do not call in contexts where this is important.
fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError>; fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError>;
/// Decrements the refcount by one and deletes the code if it drops to zero. /// Decrements the refcount by one and deletes the code if it drops to zero.
@@ -344,12 +343,22 @@ pub trait Executable<T: Config>: Sized {
/// 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.
/// ///
/// Returns the size of the original code. /// Returns the size of the original code.
fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError>; ///
/// # Note
///
/// Charges weight proportional to the code size from the gas meter.
fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
-> Result<(), 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.
/// ///
/// Returns the size of the original code. /// Returns the size of the original code.
fn remove_user(code_hash: CodeHash<T>) -> u32; ///
/// # Note
///
/// Charges weight proportional to the code size from the gas meter
fn remove_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
-> Result<(), DispatchError>;
/// Execute the specified exported function and return the result. /// Execute the specified exported function and return the result.
/// ///
@@ -595,7 +604,7 @@ where
value: BalanceOf<T>, value: BalanceOf<T>,
input_data: Vec<u8>, input_data: Vec<u8>,
debug_message: Option<&'a mut Vec<u8>>, debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> { ) -> Result<ExecReturnValue, ExecError> {
let (mut stack, executable) = Self::new( let (mut stack, executable) = Self::new(
FrameArgs::Call{dest, cached_info: None}, FrameArgs::Call{dest, cached_info: None},
origin, origin,
@@ -639,11 +648,9 @@ where
schedule, schedule,
value, value,
debug_message, debug_message,
).map_err(|(e, _code_len)| e)?; )?;
let account_id = stack.top_frame().account_id.clone(); let account_id = stack.top_frame().account_id.clone();
stack.run(executable, input_data) stack.run(executable, input_data).map(|ret| (account_id, ret))
.map(|(ret, _code_len)| (account_id, ret))
.map_err(|(err, _code_len)| err)
} }
/// Create a new call stack. /// Create a new call stack.
@@ -654,7 +661,7 @@ where
schedule: &'a Schedule<T>, schedule: &'a Schedule<T>,
value: BalanceOf<T>, value: BalanceOf<T>,
debug_message: Option<&'a mut Vec<u8>>, debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(Self, E), (ExecError, u32)> { ) -> Result<(Self, E), ExecError> {
let (first_frame, executable) = Self::new_frame(args, value, gas_meter, 0, &schedule)?; let (first_frame, executable) = Self::new_frame(args, value, gas_meter, 0, &schedule)?;
let stack = Self { let stack = Self {
origin, origin,
@@ -682,22 +689,20 @@ where
gas_meter: &mut GasMeter<T>, gas_meter: &mut GasMeter<T>,
gas_limit: Weight, gas_limit: Weight,
schedule: &Schedule<T> schedule: &Schedule<T>
) -> Result<(Frame<T>, E), (ExecError, u32)> { ) -> Result<(Frame<T>, E), ExecError> {
let (account_id, contract_info, executable, entry_point) = match frame_args { let (account_id, contract_info, executable, entry_point) = match frame_args {
FrameArgs::Call{dest, cached_info} => { FrameArgs::Call{dest, cached_info} => {
let contract = if let Some(contract) = cached_info { let contract = if let Some(contract) = cached_info {
contract contract
} else { } else {
<ContractInfoOf<T>>::get(&dest) <ContractInfoOf<T>>::get(&dest)
.ok_or((<Error<T>>::ContractNotFound.into(), 0)) .ok_or(<Error<T>>::ContractNotFound.into())
.and_then(|contract| .and_then(|contract|
contract.get_alive() contract.get_alive().ok_or(<Error<T>>::ContractIsTombstone)
.ok_or((<Error<T>>::ContractIsTombstone.into(), 0))
)? )?
}; };
let executable = E::from_storage(contract.code_hash, schedule, gas_meter) let executable = E::from_storage(contract.code_hash, schedule, gas_meter)?;
.map_err(|e| (e.into(), 0))?;
// 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
@@ -705,9 +710,8 @@ where
// 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> let contract = Rent::<T, E>
::charge(&dest, contract, executable.occupied_storage()) ::charge(&dest, contract, executable.occupied_storage())?
.map_err(|e| (e.into(), executable.code_len()))? .ok_or(Error::<T>::RentNotPaid)?;
.ok_or((Error::<T>::RentNotPaid.into(), executable.code_len()))?;
(dest, contract, executable, ExportedFunction::Call) (dest, contract, executable, ExportedFunction::Call)
} }
FrameArgs::Instantiate{sender, trie_seed, executable, salt} => { FrameArgs::Instantiate{sender, trie_seed, executable, salt} => {
@@ -719,7 +723,7 @@ where
&account_id, &account_id,
trie_id, trie_id,
executable.code_hash().clone(), executable.code_hash().clone(),
).map_err(|e| (e.into(), executable.code_len()))?; )?;
(account_id, contract, executable, ExportedFunction::Constructor) (account_id, contract, executable, ExportedFunction::Constructor)
} }
}; };
@@ -732,8 +736,7 @@ where
contract_info: CachedContract::Cached(contract_info), contract_info: CachedContract::Cached(contract_info),
account_id, account_id,
entry_point, entry_point,
nested_meter: gas_meter.nested(gas_limit) nested_meter: gas_meter.nested(gas_limit)?,
.map_err(|e| (e.into(), executable.code_len()))?,
allows_reentry: true, allows_reentry: true,
}; };
@@ -746,9 +749,9 @@ where
frame_args: FrameArgs<T, E>, frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>, value_transferred: BalanceOf<T>,
gas_limit: Weight, gas_limit: Weight,
) -> Result<E, (ExecError, u32)> { ) -> Result<E, ExecError> {
if self.frames.len() == T::CallStack::size() { if self.frames.len() == T::CallStack::size() {
return Err((Error::<T>::MaxCallDepthReached.into(), 0)); return Err(Error::<T>::MaxCallDepthReached.into());
} }
// We need to make sure that changes made to the contract info are not discarded. // We need to make sure that changes made to the contract info are not discarded.
@@ -787,7 +790,7 @@ where
&mut self, &mut self,
executable: E, executable: E,
input_data: Vec<u8> input_data: Vec<u8>
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> { ) -> Result<ExecReturnValue, ExecError> {
let entry_point = self.top_frame().entry_point; let entry_point = self.top_frame().entry_point;
let do_transaction = || { let do_transaction = || {
// Cache the value before calling into the constructor because that // Cache the value before calling into the constructor because that
@@ -795,17 +798,16 @@ where
// the same code hash we still charge the "1 block rent" as if they weren't // the same code hash we still charge the "1 block rent" as if they weren't
// spawned. This is OK as overcharging is always safe. // spawned. This is OK as overcharging is always safe.
let occupied_storage = executable.occupied_storage(); let occupied_storage = executable.occupied_storage();
let code_len = executable.code_len();
// Every call or instantiate also optionally transferres balance. // Every call or instantiate also optionally transferres balance.
self.initial_transfer().map_err(|e| (ExecError::from(e), code_len))?; self.initial_transfer()?;
// Call into the wasm blob. // Call into the wasm blob.
let output = executable.execute( let output = executable.execute(
self, self,
&entry_point, &entry_point,
input_data, input_data,
).map_err(|e| (ExecError { error: e.error, origin: ErrorOrigin::Callee }, code_len))?; ).map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?;
// Additional work needs to be performed in case of an instantiation. // Additional work needs to be performed in case of an instantiation.
if output.is_success() && entry_point == ExportedFunction::Constructor { if output.is_success() && entry_point == ExportedFunction::Constructor {
@@ -814,7 +816,7 @@ where
// It is not allowed to terminate a contract inside its constructor. // It is not allowed to terminate a contract inside its constructor.
if let CachedContract::Terminated = frame.contract_info { if let CachedContract::Terminated = frame.contract_info {
return Err((Error::<T>::TerminatedInConstructor.into(), code_len)); return Err(Error::<T>::TerminatedInConstructor.into());
} }
// Collect the rent for the first block to prevent the creation of very large // Collect the rent for the first block to prevent the creation of very large
@@ -823,9 +825,8 @@ where
// in order to keep up the guarantuee that we always leave a tombstone behind // in order to keep up the guarantuee that we always leave a tombstone behind
// with the exception of a contract that called `seal_terminate`. // with the exception of a contract that called `seal_terminate`.
let contract = Rent::<T, E> let contract = Rent::<T, E>
::charge(&account_id, frame.invalidate(), occupied_storage) ::charge(&account_id, frame.invalidate(), occupied_storage)?
.map_err(|e| (e.into(), code_len))? .ok_or(Error::<T>::NewContractNotFunded)?;
.ok_or((Error::<T>::NewContractNotFunded.into(), code_len))?;
frame.contract_info = CachedContract::Cached(contract); frame.contract_info = CachedContract::Cached(contract);
// Deposit an instantiation event. // Deposit an instantiation event.
@@ -835,7 +836,7 @@ where
)); ));
} }
Ok((output, code_len)) Ok(output)
}; };
// All changes performed by the contract are executed under a storage transaction. // All changes performed by the contract are executed under a storage transaction.
@@ -843,8 +844,8 @@ where
// comitted or rolled back when popping the frame. // comitted or rolled back when popping the frame.
let (success, output) = with_transaction(|| { let (success, output) = with_transaction(|| {
let output = do_transaction(); let output = do_transaction();
match output { match &output {
Ok((ref result, _)) if result.is_success() => { Ok(result) if result.is_success() => {
TransactionOutcome::Commit((true, output)) TransactionOutcome::Commit((true, output))
}, },
_ => TransactionOutcome::Rollback((false, output)), _ => TransactionOutcome::Rollback((false, output)),
@@ -1055,7 +1056,7 @@ where
value: BalanceOf<T>, value: BalanceOf<T>,
input_data: Vec<u8>, input_data: Vec<u8>,
allows_reentry: bool, allows_reentry: bool,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> { ) -> Result<ExecReturnValue, ExecError> {
// Before pushing the new frame: Protect the caller contract against reentrancy attacks. // Before pushing the new frame: Protect the caller contract against reentrancy attacks.
// It is important to do this before calling `allows_reentry` so that a direct recursion // It is important to do this before calling `allows_reentry` so that a direct recursion
// is caught by it. // is caught by it.
@@ -1063,7 +1064,7 @@ where
let try_call = || { let try_call = || {
if !self.allows_reentry(&to) { if !self.allows_reentry(&to) {
return Err((<Error<T>>::ReentranceDenied.into(), 0)); return Err(<Error<T>>::ReentranceDenied.into());
} }
// We ignore instantiate frames in our search for a cached contract. // We ignore instantiate frames in our search for a cached contract.
// Otherwise it would be possible to recursively call a contract from its own // Otherwise it would be possible to recursively call a contract from its own
@@ -1101,9 +1102,8 @@ where
endowment: BalanceOf<T>, endowment: BalanceOf<T>,
input_data: Vec<u8>, input_data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue, u32), (ExecError, u32)> { ) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter()) let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?;
.map_err(|e| (e.into(), 0))?;
let trie_seed = self.next_trie_seed(); let trie_seed = self.next_trie_seed();
let executable = self.push_frame( let executable = self.push_frame(
FrameArgs::Instantiate { FrameArgs::Instantiate {
@@ -1116,33 +1116,29 @@ where
gas_limit, gas_limit,
)?; )?;
let account_id = self.top_frame().account_id.clone(); let account_id = self.top_frame().account_id.clone();
self.run(executable, input_data) self.run(executable, input_data).map(|ret| (account_id, ret))
.map(|(ret, code_len)| (account_id, ret, code_len))
} }
fn terminate( fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)> {
if self.is_recursive() { if self.is_recursive() {
return Err((Error::<T>::TerminatedWhileReentrant.into(), 0)); return Err(Error::<T>::TerminatedWhileReentrant.into());
} }
let frame = self.top_frame_mut(); let frame = self.top_frame_mut();
let info = frame.terminate(); let info = frame.terminate();
Storage::<T>::queue_trie_for_deletion(&info).map_err(|e| (e, 0))?; Storage::<T>::queue_trie_for_deletion(&info)?;
<Stack<'a, T, E>>::transfer( <Stack<'a, T, E>>::transfer(
true, true,
true, true,
&frame.account_id, &frame.account_id,
beneficiary, beneficiary,
T::Currency::free_balance(&frame.account_id), T::Currency::free_balance(&frame.account_id),
).map_err(|e| (e, 0))?; )?;
ContractInfoOf::<T>::remove(&frame.account_id); ContractInfoOf::<T>::remove(&frame.account_id);
let code_len = E::remove_user(info.code_hash); E::remove_user(info.code_hash, &mut frame.nested_meter)?;
Contracts::<T>::deposit_event( Contracts::<T>::deposit_event(
Event::Terminated(frame.account_id.clone(), beneficiary.clone()), Event::Terminated(frame.account_id.clone(), beneficiary.clone()),
); );
Ok(code_len) Ok(())
} }
fn restore_to( fn restore_to(
@@ -1151,30 +1147,33 @@ 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>,
) -> Result<(u32, u32), (DispatchError, u32, u32)> { ) -> Result<(), DispatchError> {
if self.is_recursive() { if self.is_recursive() {
return Err((Error::<T>::TerminatedWhileReentrant.into(), 0, 0)); return Err(Error::<T>::TerminatedWhileReentrant.into());
} }
let origin_contract = self.top_frame_mut().contract_info().clone(); let frame = self.top_frame_mut();
let origin_contract = frame.contract_info().clone();
let account_id = frame.account_id.clone();
let result = Rent::<T, E>::restore_to( let result = Rent::<T, E>::restore_to(
&self.top_frame().account_id, &account_id,
origin_contract, origin_contract,
dest.clone(), dest.clone(),
code_hash.clone(), code_hash.clone(),
rent_allowance, rent_allowance,
delta, delta,
&mut frame.nested_meter,
); );
if let Ok(_) = result { if let Ok(_) = result {
deposit_event::<Self::T>( deposit_event::<Self::T>(
vec![], vec![],
Event::Restored( Event::Restored(
self.top_frame().account_id.clone(), account_id,
dest, dest,
code_hash, code_hash,
rent_allowance, rent_allowance,
), ),
); );
self.top_frame_mut().terminate(); frame.terminate();
} }
result result
} }
@@ -1463,14 +1462,18 @@ mod tests {
MockLoader::decrement_refcount(self.code_hash); MockLoader::decrement_refcount(self.code_hash);
} }
fn add_user(code_hash: CodeHash<Test>) -> Result<u32, DispatchError> { fn add_user(code_hash: CodeHash<Test>, _: &mut GasMeter<Test>)
-> Result<(), DispatchError>
{
MockLoader::increment_refcount(code_hash); MockLoader::increment_refcount(code_hash);
Ok(0) Ok(())
} }
fn remove_user(code_hash: CodeHash<Test>) -> u32 { fn remove_user(code_hash: CodeHash<Test>, _: &mut GasMeter<Test>)
-> Result<(), DispatchError>
{
MockLoader::decrement_refcount(code_hash); MockLoader::decrement_refcount(code_hash);
0 Ok(())
} }
fn execute<E: Ext<T = Test>>( fn execute<E: Ext<T = Test>>(
@@ -1597,7 +1600,7 @@ mod tests {
None, None,
).unwrap(); ).unwrap();
assert!(!output.0.is_success()); assert!(!output.is_success());
assert_eq!(get_balance(&origin), 100); assert_eq!(get_balance(&origin), 100);
// the rent is still charged // the rent is still charged
@@ -1658,8 +1661,8 @@ mod tests {
); );
let output = result.unwrap(); let output = result.unwrap();
assert!(output.0.is_success()); assert!(output.is_success());
assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4])); assert_eq!(output.data, Bytes(vec![1, 2, 3, 4]));
}); });
} }
@@ -1689,8 +1692,8 @@ mod tests {
); );
let output = result.unwrap(); let output = result.unwrap();
assert!(!output.0.is_success()); assert!(!output.is_success());
assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4])); assert_eq!(output.data, Bytes(vec![1, 2, 3, 4]));
}); });
} }
@@ -1770,7 +1773,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(), 0)) Err(Error::<Test>::MaxCallDepthReached.into())
); );
*reached_bottom = true; *reached_bottom = true;
} else { } else {
@@ -2000,7 +2003,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(
0, 0,
dummy_ch, dummy_ch,
Contracts::<Test>::subsistence_threshold() * 3, Contracts::<Test>::subsistence_threshold() * 3,
@@ -2053,10 +2056,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()
@@ -2293,7 +2296,7 @@ mod tests {
assert_ne!(original_allowance, changed_allowance); assert_ne!(original_allowance, changed_allowance);
ctx.ext.set_rent_allowance(changed_allowance); ctx.ext.set_rent_allowance(changed_allowance);
assert_eq!( assert_eq!(
ctx.ext.call(0, CHARLIE, 0, vec![], true).map(|v| v.0).map_err(|e| e.0), ctx.ext.call(0, CHARLIE, 0, vec![], true),
exec_trapped() exec_trapped()
); );
assert_eq!(ctx.ext.rent_allowance(), changed_allowance); assert_eq!(ctx.ext.rent_allowance(), changed_allowance);
@@ -2330,7 +2333,7 @@ mod tests {
let code = MockLoader::insert(Constructor, |ctx, _| { let code = MockLoader::insert(Constructor, |ctx, _| {
assert_matches!( assert_matches!(
ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![], true), ctx.ext.call(0, ctx.ext.address().clone(), 0, vec![], true),
Err((ExecError{error, ..}, _)) if error == <Error<Test>>::ContractNotFound.into() Err(ExecError{error, ..}) if error == <Error<Test>>::ContractNotFound.into()
); );
exec_success() exec_success()
}); });
@@ -2426,7 +2429,7 @@ mod tests {
// call the contract passed as input with disabled reentry // call the contract passed as input with disabled reentry
let code_bob = MockLoader::insert(Call, |ctx, _| { let code_bob = MockLoader::insert(Call, |ctx, _| {
let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap();
ctx.ext.call(0, dest, 0, vec![], false).map(|v| v.0).map_err(|e| e.0) ctx.ext.call(0, dest, 0, vec![], false)
}); });
let code_charlie = MockLoader::insert(Call, |_, _| { let code_charlie = MockLoader::insert(Call, |_, _| {
@@ -2459,7 +2462,7 @@ mod tests {
0, 0,
BOB.encode(), BOB.encode(),
None, None,
).map_err(|e| e.0.error), ).map_err(|e| e.error),
<Error<Test>>::ReentranceDenied, <Error<Test>>::ReentranceDenied,
); );
}); });
@@ -2469,7 +2472,7 @@ mod tests {
fn call_deny_reentry() { fn call_deny_reentry() {
let code_bob = MockLoader::insert(Call, |ctx, _| { let code_bob = MockLoader::insert(Call, |ctx, _| {
if ctx.input_data[0] == 0 { if ctx.input_data[0] == 0 {
ctx.ext.call(0, CHARLIE, 0, vec![], false).map(|v| v.0).map_err(|e| e.0) ctx.ext.call(0, CHARLIE, 0, vec![], false)
} else { } else {
exec_success() exec_success()
} }
@@ -2477,7 +2480,7 @@ mod tests {
// call BOB with input set to '1' // call BOB with input set to '1'
let code_charlie = MockLoader::insert(Call, |ctx, _| { let code_charlie = MockLoader::insert(Call, |ctx, _| {
ctx.ext.call(0, BOB, 0, vec![1], true).map(|v| v.0).map_err(|e| e.0) ctx.ext.call(0, BOB, 0, vec![1], true)
}); });
ExtBuilder::default().build().execute_with(|| { ExtBuilder::default().build().execute_with(|| {
@@ -2495,7 +2498,7 @@ mod tests {
0, 0,
vec![0], vec![0],
None, None,
).map_err(|e| e.0.error), ).map_err(|e| e.error),
<Error<Test>>::ReentranceDenied, <Error<Test>>::ReentranceDenied,
); );
}); });
-9
View File
@@ -167,15 +167,6 @@ where
self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
} }
/// Refund previously charged gas back to the gas meter.
///
/// This can be used if a gas worst case estimation must be charged before
/// performing a certain action. This way the difference can be refundend when
/// the worst case did not happen.
pub fn refund(&mut self, amount: ChargedAmount) {
self.gas_left = self.gas_left.saturating_add(amount.0).min(self.gas_limit)
}
/// Returns how much gas was used. /// Returns how much gas was used.
pub fn gas_spent(&self) -> Weight { pub fn gas_spent(&self) -> Weight {
self.gas_limit - self.gas_left self.gas_limit - self.gas_left
+7 -16
View File
@@ -275,9 +275,7 @@ pub mod pallet {
/// * 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.
#[pallet::weight(T::WeightInfo::call(T::Schedule::get().limits.code_len / 1024) #[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))]
.saturating_add(*gas_limit)
)]
pub fn call( pub fn call(
origin: OriginFor<T>, origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source, dest: <T::Lookup as StaticLookup>::Source,
@@ -289,13 +287,10 @@ pub mod pallet {
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 schedule = T::Schedule::get(); let schedule = T::Schedule::get();
let (result, code_len) = match ExecStack::<T, PrefabWasmModule<T>>::run_call( let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
origin, dest, &mut gas_meter, &schedule, value, data, None, origin, dest, &mut gas_meter, &schedule, value, data, None,
) { );
Ok((output, len)) => (Ok(output), len), gas_meter.into_dispatch_result(result, T::WeightInfo::call())
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
@@ -357,10 +352,7 @@ pub mod pallet {
/// 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.
#[pallet::weight( #[pallet::weight(
T::WeightInfo::instantiate( T::WeightInfo::instantiate(salt.len() as u32 / 1024).saturating_add(*gas_limit)
T::Schedule::get().limits.code_len / 1024, salt.len() as u32 / 1024
)
.saturating_add(*gas_limit)
)] )]
pub fn instantiate( pub fn instantiate(
origin: OriginFor<T>, origin: OriginFor<T>,
@@ -374,13 +366,12 @@ pub mod pallet {
let mut gas_meter = GasMeter::new(gas_limit); let mut gas_meter = GasMeter::new(gas_limit);
let schedule = T::Schedule::get(); let schedule = T::Schedule::get();
let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?; let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?;
let code_len = executable.code_len();
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate( let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None, origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None,
).map(|(_address, output)| output); ).map(|(_address, output)| output);
gas_meter.into_dispatch_result( gas_meter.into_dispatch_result(
result, result,
T::WeightInfo::instantiate(code_len / 1024, salt.len() as u32 / 1024), T::WeightInfo::instantiate(salt.len() as u32 / 1024),
) )
} }
@@ -666,7 +657,7 @@ where
origin, dest, &mut gas_meter, &schedule, value, input_data, debug_message.as_mut(), origin, dest, &mut gas_meter, &schedule, value, input_data, debug_message.as_mut(),
); );
ContractExecResult { ContractExecResult {
result: result.map(|r| r.0).map_err(|r| r.0.error), result: result.map_err(|r| r.error),
gas_consumed: gas_meter.gas_spent(), gas_consumed: gas_meter.gas_spent(),
debug_message: debug_message.unwrap_or_default(), debug_message: debug_message.unwrap_or_default(),
} }
+9 -13
View File
@@ -20,7 +20,7 @@
use crate::{ use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Pallet, Event, AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Pallet, Event,
TombstoneContractInfo, Config, CodeHash, Error, TombstoneContractInfo, Config, CodeHash, Error,
storage::Storage, wasm::PrefabWasmModule, exec::Executable, storage::Storage, wasm::PrefabWasmModule, exec::Executable, gas::GasMeter,
}; };
use sp_std::prelude::*; use sp_std::prelude::*;
use sp_io::hashing::blake2_256; use sp_io::hashing::blake2_256;
@@ -232,10 +232,6 @@ 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,
mut origin_contract: AliveContractInfo<T>, mut origin_contract: AliveContractInfo<T>,
@@ -243,18 +239,19 @@ where
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<(u32, u32), (DispatchError, u32, u32)> { gas_meter: &mut GasMeter<T>,
) -> Result<(), DispatchError> {
let child_trie_info = origin_contract.child_trie_info(); let child_trie_info = origin_contract.child_trie_info();
let current_block = <frame_system::Pallet<T>>::block_number(); let current_block = <frame_system::Pallet<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(), 0, 0)); return Err(Error::<T>::InvalidContractOrigin.into());
} }
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.into(), 0, 0))?; .ok_or(Error::<T>::InvalidDestinationContract)?;
let last_write = if !delta.is_empty() { let last_write = if !delta.is_empty() {
Some(current_block) Some(current_block)
@@ -263,7 +260,7 @@ where
}; };
// Fails if the code hash does not exist on chain // Fails if the code hash does not exist on chain
let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?; E::add_user(code_hash, gas_meter)?;
// 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
@@ -287,13 +284,13 @@ where
); );
if tombstone != dest_tombstone { if tombstone != dest_tombstone {
return Err((Error::<T>::InvalidTombstone.into(), caller_code_len, 0)); return Err(Error::<T>::InvalidTombstone.into());
} }
origin_contract.storage_size -= bytes_taken; origin_contract.storage_size -= bytes_taken;
<ContractInfoOf<T>>::remove(&origin); <ContractInfoOf<T>>::remove(&origin);
let tombstone_code_len = E::remove_user(origin_contract.code_hash); E::remove_user(origin_contract.code_hash, gas_meter)?;
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> { <ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(AliveContractInfo::<T> {
code_hash, code_hash,
rent_allowance, rent_allowance,
@@ -306,8 +303,7 @@ where
let origin_free_balance = T::Currency::free_balance(&origin); let origin_free_balance = T::Currency::free_balance(&origin);
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))
} }
/// Create a new `RentStatus` struct for pass through to a requesting contract. /// Create a new `RentStatus` struct for pass through to a requesting contract.
+7 -27
View File
@@ -300,18 +300,9 @@ 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,
@@ -354,9 +345,6 @@ 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,
@@ -369,9 +357,6 @@ 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,
@@ -588,11 +573,8 @@ 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_caller_code_byte: cost_byte_args!(seal_restore_to_per_code_kb_delta, 1, 0, 0), restore_to_per_delta: cost_batched!(seal_restore_to_per_delta),
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),
@@ -606,15 +588,13 @@ 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_per_code_byte: cost_byte_batched_args!(seal_call_per_code_transfer_input_output_kb, 1, 0, 0, 0), call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0),
call_transfer_surcharge: cost_batched_args!(seal_call_per_code_transfer_input_output_kb, 0, 1, 0, 0), call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0),
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_transfer_input_output_kb, 0, 0, 1),
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_code_byte: cost_byte_batched_args!(seal_instantiate_per_code_input_output_salt_kb, 1, 0, 0, 0), instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 1, 0, 0),
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_code_input_output_salt_kb, 0, 1, 0, 0), instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 1, 0),
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_input_output_salt_kb, 0, 0, 1),
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),
+63 -3
View File
@@ -363,7 +363,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 Config>::WeightInfo as WeightInfo>::call(0); let base_cost = <<Test as Config>::WeightInfo as WeightInfo>::call();
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()),
@@ -1727,6 +1727,10 @@ fn self_destruct_works() {
Ok(_) Ok(_)
); );
// The call triggers rent collection that reduces the amount of balance
// that remains for the beneficiary.
let balance_after_rent = 93_078;
pretty_assertions::assert_eq!(System::events(), vec![ pretty_assertions::assert_eq!(System::events(), vec![
EventRecord { EventRecord {
phase: Phase::Initialization, phase: Phase::Initialization,
@@ -1738,7 +1742,7 @@ fn self_destruct_works() {
EventRecord { EventRecord {
phase: Phase::Initialization, phase: Phase::Initialization,
event: Event::Balances( event: Event::Balances(
pallet_balances::Event::Transfer(addr.clone(), DJANGO, 93_086) pallet_balances::Event::Transfer(addr.clone(), DJANGO, balance_after_rent)
), ),
topics: vec![], topics: vec![],
}, },
@@ -1761,7 +1765,7 @@ fn self_destruct_works() {
// check that the beneficiary (django) got remaining balance // check that the beneficiary (django) got remaining balance
// some rent was deducted before termination // some rent was deducted before termination
assert_eq!(Balances::free_balance(DJANGO), 1_093_086); assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + balance_after_rent);
}); });
} }
@@ -2938,3 +2942,59 @@ fn debug_message_invalid_utf8() {
assert_err!(result.result, <Error<Test>>::DebugMessageInvalidUTF8); assert_err!(result.result, <Error<Test>>::DebugMessageInvalidUTF8);
}); });
} }
#[test]
fn gas_estimation_correct() {
let (caller_code, caller_hash) = compile_module::<Test>("call_return_code").unwrap();
let (callee_code, callee_hash) = compile_module::<Test>("dummy").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = Pallet::<Test>::subsistence_threshold();
let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence);
assert_ok!(
Contracts::instantiate_with_code(
Origin::signed(ALICE),
subsistence * 100,
GAS_LIMIT,
caller_code,
vec![],
vec![0],
),
);
let addr_caller = Contracts::contract_address(&ALICE, &caller_hash, &[0]);
assert_ok!(
Contracts::instantiate_with_code(
Origin::signed(ALICE),
subsistence * 100,
GAS_LIMIT,
callee_code,
vec![],
vec![1],
),
);
let addr_callee = Contracts::contract_address(&ALICE, &callee_hash, &[1]);
// Call in order to determine the gas that is required for this call
let result = Contracts::bare_call(
ALICE,
addr_caller.clone(),
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_callee).to_vec(),
false,
);
assert_ok!(result.result);
// Make the same call using the estimated gas. Should succeed.
assert_ok!(Contracts::bare_call(
ALICE,
addr_caller,
0,
result.gas_consumed,
AsRef::<[u8]>::as_ref(&addr_callee).to_vec(),
false,
).result);
});
}
@@ -81,14 +81,16 @@ 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>) -> Result<u32, DispatchError> pub fn increment_refcount<T: Config>(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
-> Result<(), DispatchError>
where where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{ {
gas_meter.charge(CodeToken::UpdateRefcount(estimate_code_size::<T>(&code_hash)?))?;
<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(module.original_code_len) Ok(())
} else { } else {
Err(Error::<T>::CodeNotFound.into()) Err(Error::<T>::CodeNotFound.into())
} }
@@ -96,23 +98,24 @@ 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>) -> u32 pub fn decrement_refcount<T: Config>(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
-> Result<(), DispatchError>
where where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{ {
if let Ok(len) = estimate_code_size::<T>(&code_hash) {
gas_meter.charge(CodeToken::UpdateRefcount(len))?;
}
<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
} }
}) });
Ok(())
} }
/// Load code with the given code hash. /// Load code with the given code hash.
@@ -120,13 +123,24 @@ where
/// If the module was instrumented with a lower version of schedule than /// If the module was instrumented with a lower version of schedule than
/// the current one given as an argument, then this function will perform /// the current one given as an argument, then this function will perform
/// re-instrumentation and update the cache in the storage. /// re-instrumentation and update the cache in the storage.
///
/// # Note
///
/// If `reinstrument` is set it is assumed that the load is performed in the context of
/// a contract call: This means we charge the size based cased for loading the contract.
pub fn load<T: Config>( pub fn load<T: Config>(
code_hash: CodeHash<T>, code_hash: CodeHash<T>,
reinstrument: Option<(&Schedule<T>, &mut GasMeter<T>)>, mut 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]>
{ {
// The reinstrument case coincides with the cases where we need to charge extra
// based upon the code size: On-chain execution.
if let Some((_, gas_meter)) = &mut reinstrument {
gas_meter.charge(CodeToken::Load(estimate_code_size::<T>(&code_hash)?))?;
}
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; prefab_module.code_hash = code_hash;
@@ -135,7 +149,7 @@ where
if prefab_module.instruction_weights_version < schedule.instruction_weights.version { if prefab_module.instruction_weights_version < schedule.instruction_weights.version {
// The instruction weights have changed. // The instruction weights have changed.
// We need to re-instrument the code with the new instruction weights. // We need to re-instrument the code with the new instruction weights.
gas_meter.charge(InstrumentToken(prefab_module.original_code_len))?; gas_meter.charge(CodeToken::Instrument(prefab_module.original_code_len))?;
private::reinstrument(&mut prefab_module, schedule)?; private::reinstrument(&mut prefab_module, schedule)?;
} }
} }
@@ -185,14 +199,50 @@ fn increment_64(refcount: &mut u64) {
"); ");
} }
/// Token to be supplied to the gas meter which charges the weight needed for reinstrumenting /// Get the size of the instrumented code stored at `code_hash` without loading it.
/// a contract of the specified size in bytes. ///
/// The returned value is slightly too large because it also contains the fields apart from
/// `code` which are located inside [`PrefabWasmModule`]. However, those are negligible when
/// compared to the code size. Additionally, charging too much weight is completely safe.
fn estimate_code_size<T: Config>(code_hash: &CodeHash<T>) -> Result<u32, DispatchError>
where
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
let key = <CodeStorage<T>>::hashed_key_for(code_hash);
let mut data = [0u8; 0];
let len = sp_io::storage::read(&key, &mut data, 0).ok_or_else(|| Error::<T>::CodeNotFound)?;
Ok(len)
}
/// Costs for operations that are related to code handling.
#[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct InstrumentToken(u32); enum CodeToken {
/// Weight for instrumenting a contract contract of the supplied size in bytes.
Instrument(u32),
/// Weight for loading a contract per kilobyte.
Load(u32),
/// Weight for changing the refcount of a contract per kilobyte.
UpdateRefcount(u32),
}
impl<T: Config> Token<T> for InstrumentToken { impl<T> Token<T> for CodeToken
where
T: Config,
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
fn weight(&self) -> Weight { fn weight(&self) -> Weight {
T::WeightInfo::instrument(self.0 / 1024) use self::CodeToken::*;
// In case of `Load` and `UpdateRefcount` we already covered the general costs of
// accessing the storage but still need to account for the actual size of the
// contract code. This is why we substract `T::*::(0)`. We need to do this at this
// point because when charging the general weight we do not know the size of
// the contract.
match *self {
Instrument(len) => T::WeightInfo::instrument(len / 1024),
Load(len) => T::WeightInfo::code_load(len / 1024).saturating_sub(T::WeightInfo::code_load(0)),
UpdateRefcount(len) =>
T::WeightInfo::code_refcount(len / 1024).saturating_sub(T::WeightInfo::code_refcount(0)),
}
} }
} }
+22 -32
View File
@@ -168,12 +168,16 @@ where
code_cache::store_decremented(self); code_cache::store_decremented(self);
} }
fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError> { fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
code_cache::increment_refcount::<T>(code_hash) -> Result<(), DispatchError>
{
code_cache::increment_refcount::<T>(code_hash, gas_meter)
} }
fn remove_user(code_hash: CodeHash<T>) -> u32 { fn remove_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
code_cache::decrement_refcount::<T>(code_hash) -> Result<(), DispatchError>
{
code_cache::decrement_refcount::<T>(code_hash, gas_meter)
} }
fn execute<E: Ext<T = T>>( fn execute<E: Ext<T = T>>(
@@ -349,14 +353,14 @@ mod tests {
value: u64, value: u64,
data: Vec<u8>, data: Vec<u8>,
allows_reentry: bool, allows_reentry: bool,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> { ) -> Result<ExecReturnValue, ExecError> {
self.calls.push(CallEntry { self.calls.push(CallEntry {
to, to,
value, value,
data, data,
allows_reentry, allows_reentry,
}); });
Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() }, 0)) Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() })
} }
fn instantiate( fn instantiate(
&mut self, &mut self,
@@ -365,7 +369,7 @@ mod tests {
endowment: u64, endowment: u64,
data: Vec<u8>, data: Vec<u8>,
salt: &[u8], salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)> { ) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError> {
self.instantiates.push(InstantiateEntry { self.instantiates.push(InstantiateEntry {
code_hash: code_hash.clone(), code_hash: code_hash.clone(),
endowment, endowment,
@@ -379,7 +383,6 @@ mod tests {
flags: ReturnFlags::empty(), flags: ReturnFlags::empty(),
data: Bytes(Vec::new()), data: Bytes(Vec::new()),
}, },
0,
)) ))
} }
fn transfer( fn transfer(
@@ -396,11 +399,11 @@ mod tests {
fn terminate( fn terminate(
&mut self, &mut self,
beneficiary: &AccountIdOf<Self::T>, beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)> { ) -> Result<(), DispatchError> {
self.terminations.push(TerminationEntry { self.terminations.push(TerminationEntry {
beneficiary: beneficiary.clone(), beneficiary: beneficiary.clone(),
}); });
Ok(0) Ok(())
} }
fn restore_to( fn restore_to(
&mut self, &mut self,
@@ -408,14 +411,14 @@ mod tests {
code_hash: H256, code_hash: H256,
rent_allowance: u64, rent_allowance: u64,
delta: Vec<StorageKey>, delta: Vec<StorageKey>,
) -> Result<(u32, u32), (DispatchError, u32, u32)> { ) -> Result<(), DispatchError> {
self.restores.push(RestoreEntry { self.restores.push(RestoreEntry {
dest, dest,
code_hash, code_hash,
rent_allowance, rent_allowance,
delta, delta,
}); });
Ok((0, 0)) Ok(())
} }
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> { fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned() self.storage.get(key).cloned()
@@ -616,7 +619,7 @@ mod tests {
fn contract_call_forward_input() { fn contract_call_forward_input() {
const CODE: &str = r#" const CODE: &str = r#"
(module (module
(import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "env" "memory" (memory 1 1)) (import "env" "memory" (memory 1 1))
(func (export "call") (func (export "call")
@@ -624,10 +627,8 @@ mod tests {
(call $seal_call (call $seal_call
(i32.const 1) ;; Set FORWARD_INPUT bit (i32.const 1) ;; Set FORWARD_INPUT bit
(i32.const 4) ;; Pointer to "callee" address. (i32.const 4) ;; Pointer to "callee" address.
(i32.const 32) ;; Length of "callee" address.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all. (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 36) ;; Pointer to the buffer with value to transfer (i32.const 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 44) ;; Pointer to input data buffer address (i32.const 44) ;; Pointer to input data buffer address
(i32.const 4) ;; Length of input data buffer (i32.const 4) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
@@ -678,7 +679,7 @@ mod tests {
fn contract_call_clone_input() { fn contract_call_clone_input() {
const CODE: &str = r#" const CODE: &str = r#"
(module (module
(import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32)))
(import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1)) (import "env" "memory" (memory 1 1))
@@ -687,10 +688,8 @@ mod tests {
(call $seal_call (call $seal_call
(i32.const 11) ;; Set FORWARD_INPUT | CLONE_INPUT | ALLOW_REENTRY bits (i32.const 11) ;; Set FORWARD_INPUT | CLONE_INPUT | ALLOW_REENTRY bits
(i32.const 4) ;; Pointer to "callee" address. (i32.const 4) ;; Pointer to "callee" address.
(i32.const 32) ;; Length of "callee" address.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all. (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 36) ;; Pointer to the buffer with value to transfer (i32.const 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 44) ;; Pointer to input data buffer address (i32.const 44) ;; Pointer to input data buffer address
(i32.const 4) ;; Length of input data buffer (i32.const 4) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
@@ -741,17 +740,15 @@ mod tests {
fn contract_call_tail_call() { fn contract_call_tail_call() {
const CODE: &str = r#" const CODE: &str = r#"
(module (module
(import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "__unstable__" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32)))
(import "env" "memory" (memory 1 1)) (import "env" "memory" (memory 1 1))
(func (export "call") (func (export "call")
(drop (drop
(call $seal_call (call $seal_call
(i32.const 5) ;; Set FORWARD_INPUT | TAIL_CALL bit (i32.const 5) ;; Set FORWARD_INPUT | TAIL_CALL bit
(i32.const 4) ;; Pointer to "callee" address. (i32.const 4) ;; Pointer to "callee" address.
(i32.const 32) ;; Length of "callee" address.
(i64.const 0) ;; How much gas to devote for the execution. 0 = all. (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
(i32.const 36) ;; Pointer to the buffer with value to transfer (i32.const 36) ;; Pointer to the buffer with value to transfer
(i32.const 8) ;; Length of the buffer with value to transfer.
(i32.const 0) ;; Pointer to input data buffer address (i32.const 0) ;; Pointer to input data buffer address
(i32.const 0) ;; Length of input data buffer (i32.const 0) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
@@ -2000,25 +1997,18 @@ mod tests {
"#; "#;
#[test] #[test]
fn contract_decode_failure() { fn contract_decode_length_ignored() {
let mut mock_ext = MockExt::default(); let mut mock_ext = MockExt::default();
let result = execute( let result = execute(
CODE_DECODE_FAILURE, CODE_DECODE_FAILURE,
vec![], vec![],
&mut mock_ext, &mut mock_ext,
); );
// AccountID implements `MaxEncodeLen` and therefore the supplied length is
assert_eq!( // no longer needed nor used to determine how much is read from contract memory.
result, assert_ok!(result);
Err(ExecError {
error: Error::<Test>::DecodingFailed.into(),
origin: ErrorOrigin::Caller,
})
);
} }
#[test] #[test]
#[cfg(feature = "unstable-interface")] #[cfg(feature = "unstable-interface")]
fn rent_params_work() { fn rent_params_work() {
+268 -195
View File
@@ -26,7 +26,7 @@ use crate::{
}; };
use bitflags::bitflags; use bitflags::bitflags;
use pwasm_utils::parity_wasm::elements::ValueType; use pwasm_utils::parity_wasm::elements::ValueType;
use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight}; use frame_support::{dispatch::DispatchError, ensure, weights::Weight, traits::MaxEncodedLen};
use sp_std::prelude::*; use sp_std::prelude::*;
use codec::{Decode, DecodeAll, Encode}; use codec::{Decode, DecodeAll, Encode};
use sp_core::{Bytes, crypto::UncheckedFrom}; use sp_core::{Bytes, crypto::UncheckedFrom};
@@ -170,12 +170,8 @@ pub enum RuntimeCosts {
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_deposit_event` with the given number of topics and event size. /// Weight of calling `seal_deposit_event` with the given number of topics and event size.
@@ -197,8 +193,6 @@ pub enum RuntimeCosts {
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.
@@ -207,8 +201,6 @@ pub enum RuntimeCosts {
/// 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.
@@ -221,8 +213,6 @@ pub enum RuntimeCosts {
HashBlake128(u32), HashBlake128(u32),
/// Weight charged by a chain extension through `seal_call_chain_extension`. /// Weight charged by a chain extension through `seal_call_chain_extension`.
ChainExtension(u64), ChainExtension(u64),
/// Weight charged for copying data from the sandbox.
CopyIn(u32),
} }
impl RuntimeCosts { impl RuntimeCosts {
@@ -250,13 +240,8 @@ impl RuntimeCosts {
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()))
@@ -272,14 +257,11 @@ impl RuntimeCosts {
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
@@ -291,7 +273,6 @@ impl RuntimeCosts {
HashBlake128(len) => s.hash_blake2_128 HashBlake128(len) => s.hash_blake2_128
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())), .saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
ChainExtension(amount) => amount, ChainExtension(amount) => amount,
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
}; };
RuntimeToken { RuntimeToken {
#[cfg(test)] #[cfg(test)]
@@ -476,15 +457,6 @@ where
self.ext.gas_meter().charge(token) self.ext.gas_meter().charge(token)
} }
/// Correct previously charged gas amount.
pub fn adjust_gas(&mut self, charged_amount: ChargedAmount, adjusted_amount: RuntimeCosts) {
let adjusted_amount = adjusted_amount.token(&self.ext.schedule().host_fn_weights);
self.ext.gas_meter().adjust_gas(
charged_amount,
adjusted_amount,
);
}
/// Read designated chunk from the sandbox memory. /// Read designated chunk from the sandbox memory.
/// ///
/// Returns `Err` if one of the following conditions occurs: /// Returns `Err` if one of the following conditions occurs:
@@ -511,6 +483,21 @@ where
self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into()) self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
} }
/// Reads and decodes a type with a size fixed at compile time from contract memory.
///
/// # Note
///
/// The weight of reading a fixed value is included in the overall weight of any
/// contract callable function.
pub fn read_sandbox_memory_as<D: Decode + MaxEncodedLen>(&self, ptr: u32)
-> Result<D, DispatchError>
{
let buf = self.read_sandbox_memory(ptr, D::max_encoded_len() as u32)?;
let decoded = D::decode_all(&mut &buf[..])
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
Ok(decoded)
}
/// Read designated chunk from the sandbox memory and attempt to decode into the specified type. /// Read designated chunk from the sandbox memory and attempt to decode into the specified type.
/// ///
/// Returns `Err` if one of the following conditions occurs: /// Returns `Err` if one of the following conditions occurs:
@@ -520,25 +507,14 @@ where
/// ///
/// # Note /// # Note
/// ///
/// It is safe to forgo benchmarking and charging weight relative to `len` for fixed /// There must be an extra benchmark for determining the influence of `len` with
/// size types (basically everything not containing a heap collection): /// regard to the overall weight.
/// Despite the fact that we are usually about to read the encoding of a fixed size pub fn read_sandbox_memory_as_unbounded<D: Decode>(&self, ptr: u32, len: u32)
/// type, we cannot know the encoded size of that type. We therefore are required to
/// use the length provided by the contract. This length is untrusted and therefore
/// we charge weight relative to the provided size upfront that covers the copy costs.
/// On success this cost is refunded as the copying was already covered in the
/// overall cost of the host function. This is different from `read_sandbox_memory`
/// where the size is dynamic and the costs resulting from that dynamic size must
/// be charged relative to this dynamic size anyways (before reading) by constructing
/// the benchmark for that.
pub fn read_sandbox_memory_as<D: Decode>(&mut self, ptr: u32, len: u32)
-> Result<D, DispatchError> -> Result<D, DispatchError>
{ {
let amount = self.charge_gas(RuntimeCosts::CopyIn(len))?;
let buf = self.read_sandbox_memory(ptr, len)?; let buf = self.read_sandbox_memory(ptr, len)?;
let decoded = D::decode_all(&mut &buf[..]) let decoded = D::decode_all(&mut &buf[..])
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?; .map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
self.ext.gas_meter().refund(amount);
Ok(decoded) Ok(decoded)
} }
@@ -575,7 +551,7 @@ where
} }
let buf_len = buf.len() as u32; let buf_len = buf.len() as u32;
let len: u32 = self.read_sandbox_memory_as(out_len_ptr, 4)?; let len: u32 = self.read_sandbox_memory_as(out_len_ptr)?;
if len < buf_len { if len < buf_len {
Err(Error::<E::T>::OutputBufferTooSmall)? Err(Error::<E::T>::OutputBufferTooSmall)?
@@ -675,19 +651,18 @@ where
&mut self, &mut self,
flags: CallFlags, flags: CallFlags,
callee_ptr: u32, callee_ptr: u32,
callee_len: u32,
gas: u64, gas: u64,
value_ptr: u32, value_ptr: u32,
value_len: u32,
input_data_ptr: u32, input_data_ptr: u32,
input_data_len: u32, input_data_len: u32,
output_ptr: u32, output_ptr: u32,
output_len_ptr: u32 output_len_ptr: u32
) -> Result<ReturnCode, TrapReason> { ) -> Result<ReturnCode, TrapReason>
{
self.charge_gas(RuntimeCosts::CallBase(input_data_len))?; self.charge_gas(RuntimeCosts::CallBase(input_data_len))?;
let callee: <<E as Ext>::T as frame_system::Config>::AccountId = let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(callee_ptr, callee_len)?; self.read_sandbox_memory_as(callee_ptr)?;
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr, value_len)?; let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
let input_data = if flags.contains(CallFlags::CLONE_INPUT) { let input_data = if flags.contains(CallFlags::CLONE_INPUT) {
self.input_data.as_ref().ok_or_else(|| Error::<E::T>::InputForwarded)?.clone() self.input_data.as_ref().ok_or_else(|| Error::<E::T>::InputForwarded)?.clone()
} else if flags.contains(CallFlags::FORWARD_INPUT) { } else if flags.contains(CallFlags::FORWARD_INPUT) {
@@ -698,23 +673,15 @@ where
if value > 0u32.into() { if value > 0u32.into() {
self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?; self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?;
} }
let charged = self.charge_gas(
RuntimeCosts::CallSurchargeCodeSize(<E::T as Config>::Schedule::get().limits.code_len)
)?;
let ext = &mut self.ext; let ext = &mut self.ext;
let call_outcome = ext.call( let call_outcome = ext.call(
gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY), gas, callee, value, input_data, flags.contains(CallFlags::ALLOW_REENTRY),
); );
let code_len = match &call_outcome {
Ok((_, len)) => len,
Err((_, len)) => len,
};
self.adjust_gas(charged, RuntimeCosts::CallSurchargeCodeSize(*code_len));
// `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to // `TAIL_CALL` only matters on an `OK` result. Otherwise the call stack comes to
// a halt anyways without anymore code being executed. // a halt anyways without anymore code being executed.
if flags.contains(CallFlags::TAIL_CALL) { if flags.contains(CallFlags::TAIL_CALL) {
if let Ok((return_value, _)) = call_outcome { if let Ok(return_value) = call_outcome {
return Err(TrapReason::Return(ReturnData { return Err(TrapReason::Return(ReturnData {
flags: return_value.flags.bits(), flags: return_value.flags.bits(),
data: return_value.data.0, data: return_value.data.0,
@@ -722,12 +689,98 @@ where
} }
} }
if let Ok((output, _)) = &call_outcome { if let Ok(output) = &call_outcome {
self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
Some(RuntimeCosts::CallCopyOut(len)) Some(RuntimeCosts::CallCopyOut(len))
})?; })?;
} }
Ok(Runtime::<E>::exec_into_return_code(call_outcome.map(|r| r.0).map_err(|r| r.0))?) Ok(Runtime::<E>::exec_into_return_code(call_outcome)?)
}
fn instantiate(
&mut self,
code_hash_ptr: u32,
gas: u64,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
address_ptr: u32,
address_len_ptr: u32,
output_ptr: u32,
output_len_ptr: u32,
salt_ptr: u32,
salt_len: u32
) -> Result<ReturnCode, TrapReason>
{
self.charge_gas(RuntimeCosts::InstantiateBase {input_data_len, salt_len})?;
let code_hash: CodeHash<<E as Ext>::T> = self.read_sandbox_memory_as(code_hash_ptr)?;
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
let input_data = self.read_sandbox_memory(input_data_ptr, input_data_len)?;
let salt = self.read_sandbox_memory(salt_ptr, salt_len)?;
let instantiate_outcome = self.ext.instantiate(gas, code_hash, value, input_data, &salt);
if let Ok((address, output)) = &instantiate_outcome {
if !output.flags.contains(ReturnFlags::REVERT) {
self.write_sandbox_output(
address_ptr, address_len_ptr, &address.encode(), true, already_charged,
)?;
}
self.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| {
Some(RuntimeCosts::InstantiateCopyOut(len))
})?;
}
Ok(Runtime::<E>::exec_into_return_code(instantiate_outcome.map(|(_, retval)| retval))?)
}
fn terminate(&mut self, beneficiary_ptr: u32) -> Result<(), TrapReason> {
self.charge_gas(RuntimeCosts::Terminate)?;
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(beneficiary_ptr)?;
self.ext.terminate(&beneficiary)?;
Err(TrapReason::Termination)
}
fn restore_to(
&mut self,
dest_ptr: u32,
code_hash_ptr: u32,
rent_allowance_ptr: u32,
delta_ptr: u32,
delta_count: u32
) -> Result<(), TrapReason> {
self.charge_gas(RuntimeCosts::RestoreTo(delta_count))?;
let dest: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(dest_ptr)?;
let code_hash: CodeHash<<E as Ext>::T> =
self.read_sandbox_memory_as(code_hash_ptr)?;
let rent_allowance: BalanceOf<<E as Ext>::T> =
self.read_sandbox_memory_as(rent_allowance_ptr)?;
let delta = {
const KEY_SIZE: usize = 32;
// We can eagerly allocate because we charged for the complete delta count already
// We still need to make sure that the allocation isn't larger than the memory
// allocator can handle.
let max_memory = self.ext.schedule().limits.max_memory_size();
ensure!(
delta_count.saturating_mul(KEY_SIZE as u32) <= max_memory,
Error::<E::T>::OutOfBounds,
);
let mut delta = vec![[0; KEY_SIZE]; delta_count as usize];
let mut key_ptr = delta_ptr;
for i in 0..delta_count {
// Read the delta into the provided buffer
// This cannot panic because of the loop condition
self.read_sandbox_memory_into_buf(key_ptr, &mut delta[i as usize])?;
// Offset key_ptr to the next element.
key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or(Error::<E::T>::OutOfBounds)?;
}
delta
};
self.ext.restore_to(dest, code_hash, rent_allowance, delta)?;
Err(TrapReason::Restoration)
} }
} }
@@ -838,15 +891,15 @@ define_env!(Env, <E: Ext>,
[seal0] seal_transfer( [seal0] seal_transfer(
ctx, ctx,
account_ptr: u32, account_ptr: u32,
account_len: u32, _account_len: u32,
value_ptr: u32, value_ptr: u32,
value_len: u32 _value_len: u32
) -> ReturnCode => { ) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::Transfer)?; ctx.charge_gas(RuntimeCosts::Transfer)?;
let callee: <<E as Ext>::T as frame_system::Config>::AccountId = let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(account_ptr, account_len)?; ctx.read_sandbox_memory_as(account_ptr)?;
let value: BalanceOf<<E as Ext>::T> = let value: BalanceOf<<E as Ext>::T> =
ctx.read_sandbox_memory_as(value_ptr, value_len)?; ctx.read_sandbox_memory_as(value_ptr)?;
let result = ctx.ext.transfer(&callee, value); let result = ctx.ext.transfer(&callee, value);
match result { match result {
@@ -860,15 +913,23 @@ define_env!(Env, <E: Ext>,
// Make a call to another contract. // Make a call to another contract.
// //
// # Deprecation
//
// This is equivalent to calling the newer version of this function with // This is equivalent to calling the newer version of this function with
// `flags` set to `ALLOW_REENTRY`. See the newer version for documentation. // `flags` set to `ALLOW_REENTRY`. See the newer version for documentation.
//
// # Note
//
// The values `_callee_len` and `_value_len` are ignored because the encoded sizes
// of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards
// compatibility. Consider switching to the newest version of this function.
[seal0] seal_call( [seal0] seal_call(
ctx, ctx,
callee_ptr: u32, callee_ptr: u32,
callee_len: u32, _callee_len: u32,
gas: u64, gas: u64,
value_ptr: u32, value_ptr: u32,
value_len: u32, _value_len: u32,
input_data_ptr: u32, input_data_ptr: u32,
input_data_len: u32, input_data_len: u32,
output_ptr: u32, output_ptr: u32,
@@ -877,10 +938,8 @@ define_env!(Env, <E: Ext>,
ctx.call( ctx.call(
CallFlags::ALLOW_REENTRY, CallFlags::ALLOW_REENTRY,
callee_ptr, callee_ptr,
callee_len,
gas, gas,
value_ptr, value_ptr,
value_len,
input_data_ptr, input_data_ptr,
input_data_len, input_data_len,
output_ptr, output_ptr,
@@ -899,11 +958,9 @@ define_env!(Env, <E: Ext>,
// - flags: See [`CallFlags`] for a documenation of the supported flags. // - flags: See [`CallFlags`] for a documenation of the supported flags.
// - callee_ptr: a pointer to the address of the callee contract. // - callee_ptr: a pointer to the address of the callee contract.
// Should be decodable as an `T::AccountId`. Traps otherwise. // Should be decodable as an `T::AccountId`. Traps otherwise.
// - callee_len: length of the address buffer.
// - gas: how much gas to devote to the execution. // - gas: how much gas to devote to the execution.
// - value_ptr: a pointer to the buffer with value, how much value to send. // - value_ptr: a pointer to the buffer with value, how much value to send.
// Should be decodable as a `T::Balance`. Traps otherwise. // Should be decodable as a `T::Balance`. Traps otherwise.
// - value_len: length of the value buffer.
// - input_data_ptr: a pointer to a buffer to be used as input data to the callee. // - input_data_ptr: a pointer to a buffer to be used as input data to the callee.
// - input_data_len: length of the input data buffer. // - input_data_len: length of the input data buffer.
// - output_ptr: a pointer where the output buffer is copied to. // - output_ptr: a pointer where the output buffer is copied to.
@@ -924,10 +981,8 @@ define_env!(Env, <E: Ext>,
ctx, ctx,
flags: u32, flags: u32,
callee_ptr: u32, callee_ptr: u32,
callee_len: u32,
gas: u64, gas: u64,
value_ptr: u32, value_ptr: u32,
value_len: u32,
input_data_ptr: u32, input_data_ptr: u32,
input_data_len: u32, input_data_len: u32,
output_ptr: u32, output_ptr: u32,
@@ -936,10 +991,8 @@ define_env!(Env, <E: Ext>,
ctx.call( ctx.call(
CallFlags::from_bits(flags).ok_or_else(|| "used rerved bit in CallFlags")?, CallFlags::from_bits(flags).ok_or_else(|| "used rerved bit in CallFlags")?,
callee_ptr, callee_ptr,
callee_len,
gas, gas,
value_ptr, value_ptr,
value_len,
input_data_ptr, input_data_ptr,
input_data_len, input_data_len,
output_ptr, output_ptr,
@@ -947,6 +1000,49 @@ define_env!(Env, <E: Ext>,
) )
}, },
// Instantiate a contract with the specified code hash.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function. The newer version
// drops the now unnecessary length fields.
//
// # Note
//
// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes
// of those types are fixed through `[`MaxEncodedLen`]. The fields exist for backwards
// compatibility. Consider switching to the newest version of this function.
[seal0] seal_instantiate(
ctx,
code_hash_ptr: u32,
_code_hash_len: u32,
gas: u64,
value_ptr: u32,
_value_len: u32,
input_data_ptr: u32,
input_data_len: u32,
address_ptr: u32,
address_len_ptr: u32,
output_ptr: u32,
output_len_ptr: u32,
salt_ptr: u32,
salt_len: u32
) -> ReturnCode => {
ctx.instantiate (
code_hash_ptr,
gas,
value_ptr,
input_data_ptr,
input_data_len,
address_ptr,
address_len_ptr,
output_ptr,
output_len_ptr,
salt_ptr,
salt_len,
)
},
// Instantiate a contract with the specified code hash. // Instantiate a contract with the specified code hash.
// //
// This function creates an account and executes the constructor defined in the code specified // This function creates an account and executes the constructor defined in the code specified
@@ -962,11 +1058,9 @@ define_env!(Env, <E: Ext>,
// # Parameters // # Parameters
// //
// - code_hash_ptr: a pointer to the buffer that contains the initializer code. // - code_hash_ptr: a pointer to the buffer that contains the initializer code.
// - code_hash_len: length of the initializer code buffer.
// - gas: how much gas to devote to the execution of the initializer code. // - gas: how much gas to devote to the execution of the initializer code.
// - value_ptr: a pointer to the buffer with value, how much value to send. // - value_ptr: a pointer to the buffer with value, how much value to send.
// Should be decodable as a `T::Balance`. Traps otherwise. // Should be decodable as a `T::Balance`. Traps otherwise.
// - value_len: length of the value buffer.
// - input_data_ptr: a pointer to a buffer to be used as input data to the initializer code. // - input_data_ptr: a pointer to a buffer to be used as input data to the initializer code.
// - input_data_len: length of the input data buffer. // - input_data_len: length of the input data buffer.
// - address_ptr: a pointer where the new account's address is copied to. // - address_ptr: a pointer where the new account's address is copied to.
@@ -992,13 +1086,11 @@ define_env!(Env, <E: Ext>,
// `ReturnCode::TransferFailed` // `ReturnCode::TransferFailed`
// `ReturnCode::NewContractNotFunded` // `ReturnCode::NewContractNotFunded`
// `ReturnCode::CodeNotFound` // `ReturnCode::CodeNotFound`
[seal0] seal_instantiate( [seal1] seal_instantiate(
ctx, ctx,
code_hash_ptr: u32, code_hash_ptr: u32,
code_hash_len: u32,
gas: u64, gas: u64,
value_ptr: u32, value_ptr: u32,
value_len: u32,
input_data_ptr: u32, input_data_ptr: u32,
input_data_len: u32, input_data_len: u32,
address_ptr: u32, address_ptr: u32,
@@ -1008,37 +1100,35 @@ define_env!(Env, <E: Ext>,
salt_ptr: u32, salt_ptr: u32,
salt_len: u32 salt_len: u32
) -> ReturnCode => { ) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::InstantiateBase {input_data_len, salt_len})?; ctx.instantiate(
let code_hash: CodeHash<<E as Ext>::T> = code_hash_ptr,
ctx.read_sandbox_memory_as(code_hash_ptr, code_hash_len)?; gas,
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr, value_len)?; value_ptr,
let input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?; input_data_ptr,
let salt = ctx.read_sandbox_memory(salt_ptr, salt_len)?; input_data_len,
let charged = ctx.charge_gas( address_ptr,
RuntimeCosts::InstantiateSurchargeCodeSize( address_len_ptr,
<E::T as Config>::Schedule::get().limits.code_len output_ptr,
) output_len_ptr,
)?; salt_ptr,
let ext = &mut ctx.ext; salt_len,
let instantiate_outcome = ext.instantiate(gas, code_hash, value, input_data, &salt); )
let code_len = match &instantiate_outcome { },
Ok((_, _, code_len)) => code_len,
Err((_, code_len)) => code_len, // Remove the calling account and transfer remaining balance.
}; //
ctx.adjust_gas(charged, RuntimeCosts::InstantiateSurchargeCodeSize(*code_len)); // # Deprecation
if let Ok((address, output, _)) = &instantiate_outcome { //
if !output.flags.contains(ReturnFlags::REVERT) { // This is equivalent to calling the newer version of this function. The newer version
ctx.write_sandbox_output( // drops the now unnecessary length fields.
address_ptr, address_len_ptr, &address.encode(), true, already_charged, //
)?; // # Note
} //
ctx.write_sandbox_output(output_ptr, output_len_ptr, &output.data, true, |len| { // The value `_beneficiary_len` is ignored because the encoded sizes
Some(RuntimeCosts::InstantiateCopyOut(len)) // this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards
})?; // compatibility. Consider switching to the newest version of this function.
} [seal0] seal_terminate(ctx, beneficiary_ptr: u32, _beneficiary_len: u32) => {
Ok(Runtime::<E>::exec_into_return_code( ctx.terminate(beneficiary_ptr)
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.
@@ -1050,33 +1140,14 @@ define_env!(Env, <E: Ext>,
// - beneficiary_ptr: a pointer to the address of the beneficiary account where all // - beneficiary_ptr: a pointer to the address of the beneficiary account where all
// where all remaining funds of the caller are transferred. // where all remaining funds of the caller are transferred.
// Should be decodable as an `T::AccountId`. Traps otherwise. // Should be decodable as an `T::AccountId`. Traps otherwise.
// - beneficiary_len: length of the address buffer.
// //
// # Traps // # Traps
// //
// - The contract is live i.e is already on the call stack. // - The contract is live i.e is already on the call stack.
// - Failed to send the balance to the beneficiary. // - Failed to send the balance to the beneficiary.
// - The deletion queue is full. // - The deletion queue is full.
[seal0] seal_terminate( [seal1] seal_terminate(ctx, beneficiary_ptr: u32) => {
ctx, ctx.terminate(beneficiary_ptr)
beneficiary_ptr: u32,
beneficiary_len: u32
) => {
ctx.charge_gas(RuntimeCosts::Terminate)?;
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
let charged = ctx.charge_gas(
RuntimeCosts::TerminateSurchargeCodeSize(
<E::T as Config>::Schedule::get().limits.code_len
)
)?;
let (result, code_len) = match ctx.ext.terminate(&beneficiary) {
Ok(len) => (Ok(()), len),
Err((err, len)) => (Err(err), len),
};
ctx.adjust_gas(charged, RuntimeCosts::TerminateSurchargeCodeSize(code_len));
result?;
Err(TrapReason::Termination)
}, },
// Stores the input passed by the caller into the supplied buffer. // Stores the input passed by the caller into the supplied buffer.
@@ -1323,6 +1394,38 @@ define_env!(Env, <E: Ext>,
)?) )?)
}, },
// Try to restore the given destination contract sacrificing the caller.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function. The newer version
// drops the now unnecessary length fields.
//
// # Note
//
// The values `_dest_len`, `_code_hash_len` and `_rent_allowance_len` are ignored because
// the encoded sizes of those types are fixed through `[`MaxEncodedLen`]. The fields
// exist for backwards compatibility. Consider switching to the newest version of this function.
[seal0] seal_restore_to(
ctx,
dest_ptr: u32,
_dest_len: u32,
code_hash_ptr: u32,
_code_hash_len: u32,
rent_allowance_ptr: u32,
_rent_allowance_len: u32,
delta_ptr: u32,
delta_count: u32
) => {
ctx.restore_to(
dest_ptr,
code_hash_ptr,
rent_allowance_ptr,
delta_ptr,
delta_count,
)
},
// Try to restore the given destination contract sacrificing the caller. // Try to restore the given destination contract sacrificing the caller.
// //
// This function will compute a tombstone hash from the caller's storage and the given code hash // This function will compute a tombstone hash from the caller's storage and the given code hash
@@ -1339,11 +1442,11 @@ define_env!(Env, <E: Ext>,
// On success, the destination contract is restored. This function is diverging and // On success, the destination contract is restored. This function is diverging and
// stops execution even on success. // stops execution even on success.
// //
// - `dest_ptr`, `dest_len` - the pointer and the length of a buffer that encodes `T::AccountId` // - `dest_ptr` - the pointer to a buffer that encodes `T::AccountId`
// with the address of the to be restored contract. // with the address of the to be restored contract.
// - `code_hash_ptr`, `code_hash_len` - the pointer and the length of a buffer that encodes // - `code_hash_ptr` - the pointer to a buffer that encodes
// a code hash of the to be restored contract. // a code hash of the to be restored contract.
// - `rent_allowance_ptr`, `rent_allowance_len` - the pointer and the length of a buffer that // - `rent_allowance_ptr` - the pointer to a buffer that
// encodes the rent allowance that must be set in the case of successful restoration. // encodes the rent allowance that must be set in the case of successful restoration.
// - `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys // - `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys
// laid out sequentially. // laid out sequentially.
@@ -1354,67 +1457,21 @@ define_env!(Env, <E: Ext>,
// - Tombstone hashes do not match. // - Tombstone hashes do not match.
// - The calling contract is already present on the call stack. // - The calling contract is already present on the call stack.
// - The supplied code_hash does not exist on-chain. // - The supplied code_hash does not exist on-chain.
[seal0] seal_restore_to( [seal1] seal_restore_to(
ctx, ctx,
dest_ptr: u32, dest_ptr: u32,
dest_len: u32,
code_hash_ptr: u32, code_hash_ptr: u32,
code_hash_len: u32,
rent_allowance_ptr: u32, rent_allowance_ptr: u32,
rent_allowance_len: u32,
delta_ptr: u32, delta_ptr: u32,
delta_count: u32 delta_count: u32
) => { ) => {
ctx.charge_gas(RuntimeCosts::RestoreTo(delta_count))?; ctx.restore_to(
let dest: <<E as Ext>::T as frame_system::Config>::AccountId = dest_ptr,
ctx.read_sandbox_memory_as(dest_ptr, dest_len)?; code_hash_ptr,
let code_hash: CodeHash<<E as Ext>::T> = rent_allowance_ptr,
ctx.read_sandbox_memory_as(code_hash_ptr, code_hash_len)?; delta_ptr,
let rent_allowance: BalanceOf<<E as Ext>::T> = delta_count,
ctx.read_sandbox_memory_as(rent_allowance_ptr, rent_allowance_len)?; )
let delta = {
const KEY_SIZE: usize = 32;
// We can eagerly allocate because we charged for the complete delta count already
// We still need to make sure that the allocation isn't larger than the memory
// allocator can handle.
ensure!(
delta_count
.saturating_mul(KEY_SIZE as u32) <= ctx.ext.schedule().limits.max_memory_size(),
Error::<E::T>::OutOfBounds,
);
let mut delta = vec![[0; KEY_SIZE]; delta_count as usize];
let mut key_ptr = delta_ptr;
for i in 0..delta_count {
// Read the delta into the provided buffer
// This cannot panic because of the loop condition
ctx.read_sandbox_memory_into_buf(key_ptr, &mut delta[i as usize])?;
// Offset key_ptr to the next element.
key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or(Error::<E::T>::OutOfBounds)?;
}
delta
};
let max_len = <E::T as Config>::Schedule::get().limits.code_len;
let charged = ctx.charge_gas(RuntimeCosts::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, RuntimeCosts::RestoreToSurchargeCodeSize {
caller_code,
tombstone_code,
});
result?;
Err(TrapReason::Restoration)
}, },
// Deposit a contract event with the data buffer and optional list of topics. There is a limit // Deposit a contract event with the data buffer and optional list of topics. There is a limit
@@ -1460,7 +1517,7 @@ define_env!(Env, <E: Ext>,
let mut topics: Vec::<TopicOf<<E as Ext>::T>> = match topics_len { let mut topics: Vec::<TopicOf<<E as Ext>::T>> = match topics_len {
0 => Vec::new(), 0 => Vec::new(),
_ => ctx.read_sandbox_memory_as(topics_ptr, topics_len)?, _ => ctx.read_sandbox_memory_as_unbounded(topics_ptr, topics_len)?,
}; };
// If there are more than `event_topics`, then trap. // If there are more than `event_topics`, then trap.
@@ -1482,17 +1539,33 @@ define_env!(Env, <E: Ext>,
Ok(()) Ok(())
}, },
// Set rent allowance of the contract // Set rent allowance of the contract.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function. The newer version
// drops the now unnecessary length fields.
//
// # Note
//
// The value `_VALUE_len` is ignored because the encoded sizes
// this type is fixed through `[`MaxEncodedLen`]. The field exist for backwards
// compatibility. Consider switching to the newest version of this function.
[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, _value_len: u32) => {
ctx.charge_gas(RuntimeCosts::SetRentAllowance)?;
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr)?;
ctx.ext.set_rent_allowance(value);
Ok(())
},
// Set rent allowance of the contract.
// //
// - value_ptr: a pointer to the buffer with value, how much to allow for rent // - value_ptr: a pointer to the buffer with value, how much to allow for rent
// Should be decodable as a `T::Balance`. Traps otherwise. // Should be decodable as a `T::Balance`. Traps otherwise.
// - value_len: length of the value buffer. [seal1] seal_set_rent_allowance(ctx, value_ptr: u32) => {
[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
ctx.charge_gas(RuntimeCosts::SetRentAllowance)?; ctx.charge_gas(RuntimeCosts::SetRentAllowance)?;
let value: BalanceOf<<E as Ext>::T> = let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr)?;
ctx.read_sandbox_memory_as(value_ptr, value_len)?;
ctx.ext.set_rent_allowance(value); ctx.ext.set_rent_allowance(value);
Ok(()) Ok(())
}, },
File diff suppressed because it is too large Load Diff