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.
[#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
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
(module
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
(import "seal0" "seal_instantiate" (func $seal_instantiate
(param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
(import "seal1" "seal_instantiate" (func $seal_instantiate
(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 "env" "memory" (memory 1 1))
@@ -29,10 +29,8 @@
(i32.const 8)
(call $seal_instantiate
(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.
(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 4) ;; Length of input data buffer
(i32.const 0xffffffff) ;; u32 max sentinel value: do not copy address
@@ -32,4 +32,4 @@
;; 2 = trap
(unreachable)
)
)
)
@@ -1,9 +1,9 @@
(module
(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_restore_to"
(import "seal1" "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))
@@ -27,15 +27,12 @@
)
)
(call $seal_restore_to
;; Pointer and length of the encoded dest buffer.
;; Pointer to the encoded dest buffer.
(i32.const 340)
(i32.const 32)
;; Pointer and length of the encoded code hash buffer
;; Pointer to the encoded code hash buffer
(i32.const 308)
(i32.const 32)
;; Pointer and length of the encoded rent_allowance buffer
;; Pointer to the encoded rent_allowance buffer
(i32.const 296)
(i32.const 8)
;; Pointer and number of items in the delta buffer.
;; This buffer specifies multiple keys for removal before restoration.
(i32.const 100)
@@ -258,9 +258,14 @@ where
/// 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 {
// 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 {
memory: Some(ImportedMemory::max::<T>()),
dummy_section: dummy_bytes,
dummy_section: dummy_bytes.saturating_sub(module_overhead),
.. Default::default()
}
.into()
@@ -320,6 +320,25 @@ benchmarks! {
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.
// 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
@@ -352,16 +371,14 @@ benchmarks! {
}
// 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.
instantiate {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let s in 0 .. code::max_pages::<T>() * 64;
let salt = vec![42u8; (s * 1024) as usize];
let endowment = caller_funding::<T>() / 3u32.into();
let caller = whitelisted_caller();
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 addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
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.
// The dummy contract used here does not do this. The costs for the data copy is billed as
// part of `seal_input`.
// `c`: Size of the code in kilobytes.
call {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let data = vec![42u8; 1024];
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 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 {
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
seal_restore_to_per_code_kb_delta {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
let t in 0 .. T::Schedule::get().limits.code_len / 1024;
seal_restore_to_per_delta {
let d in 0 .. API_BENCHMARK_BATCHES;
let mut tombstone = ContractWithStorage::<T>::with_code(
WasmModule::<T>::dummy_with_bytes(t * 1024), 0, 0
)?;
let mut tombstone = ContractWithStorage::<T>::new(0, 0)?;
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_len = dest.len();
@@ -909,7 +884,6 @@ benchmarks! {
Instruction::Call(0),
Instruction::End,
])),
dummy_section: c * 1024,
.. Default::default()
});
@@ -1393,8 +1367,7 @@ benchmarks! {
let origin = RawOrigin::Signed(instance.caller.clone());
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
seal_call_per_code_transfer_input_output_kb {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
seal_call_per_transfer_input_output_kb {
let t in 0 .. 1;
let i in 0 .. code::max_pages::<T>() * 64;
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1417,7 +1390,6 @@ benchmarks! {
Instruction::Call(0),
Instruction::End,
])),
dummy_section: c * 1024,
.. Default::default()
});
let callees = (0..API_BENCHMARK_BATCH_SIZE)
@@ -1593,8 +1565,7 @@ benchmarks! {
}
}
seal_instantiate_per_code_input_output_salt_kb {
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
seal_instantiate_per_input_output_salt_kb {
let i 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;
@@ -1617,7 +1588,6 @@ benchmarks! {
Instruction::Call(0),
Instruction::End,
])),
dummy_section: c * 1024,
.. Default::default()
});
let hash = callee_code.hash.clone();
@@ -59,7 +59,7 @@ use crate::{
wasm::{Runtime, RuntimeCosts},
};
use codec::Decode;
use frame_support::weights::Weight;
use frame_support::{weights::Weight, traits::MaxEncodedLen};
use sp_runtime::DispatchError;
use sp_std::{
marker::PhantomData,
@@ -300,18 +300,21 @@ where
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
/// 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
/// are used. Non fixed size types (like everything using `Vec`) usually need to use
/// [`in_len()`](Self::in_len) in order to properly charge the necessary weight.
pub fn read_as<T: Decode>(&mut self) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as(
self.inner.input_ptr,
self.inner.input_len,
)
/// are used.
pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr)
}
/// 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`.
+90 -87
View File
@@ -168,7 +168,7 @@ pub trait Ext: sealing::Sealed {
value: BalanceOf<Self::T>,
input_data: Vec<u8>,
allows_reentry: bool,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)>;
) -> Result<ExecReturnValue, ExecError>;
/// Instantiate a contract from the given code.
///
@@ -186,24 +186,16 @@ pub trait Ext: sealing::Sealed {
value: BalanceOf<Self::T>,
input_data: Vec<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.
///
/// Returns the original code size of the terminated contract.
/// Since this function removes the self contract eagerly, if succeeded, no further actions should
/// be performed on this `Ext` instance.
///
/// This function will fail if the same contract is present on the contract
/// call stack.
///
/// # Return Value
///
/// Result<CodeSize, (DispatchError, CodeSize)>
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)>;
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError>;
/// Restores the given destination contract sacrificing the current one.
///
@@ -222,7 +214,7 @@ pub trait Ext: sealing::Sealed {
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(u32, u32), (DispatchError, u32, u32)>;
) -> Result<(), DispatchError>;
/// Transfer some amount of funds into the specified account.
fn transfer(
@@ -325,6 +317,9 @@ pub enum ExportedFunction {
/// order to be able to mock the wasm logic for testing.
pub trait Executable<T: Config>: Sized {
/// Load the executable from storage.
///
/// # Note
/// Charges size base load and instrumentation weight from the gas meter.
fn from_storage(
code_hash: CodeHash<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
/// an older schedule. This skips this step for cases where the code storage is
/// 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>;
/// 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.
///
/// 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.
///
/// 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.
///
@@ -595,7 +604,7 @@ where
value: BalanceOf<T>,
input_data: Vec<u8>,
debug_message: Option<&'a mut Vec<u8>>,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
) -> Result<ExecReturnValue, ExecError> {
let (mut stack, executable) = Self::new(
FrameArgs::Call{dest, cached_info: None},
origin,
@@ -639,11 +648,9 @@ where
schedule,
value,
debug_message,
).map_err(|(e, _code_len)| e)?;
)?;
let account_id = stack.top_frame().account_id.clone();
stack.run(executable, input_data)
.map(|(ret, _code_len)| (account_id, ret))
.map_err(|(err, _code_len)| err)
stack.run(executable, input_data).map(|ret| (account_id, ret))
}
/// Create a new call stack.
@@ -654,7 +661,7 @@ where
schedule: &'a Schedule<T>,
value: BalanceOf<T>,
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 stack = Self {
origin,
@@ -682,22 +689,20 @@ where
gas_meter: &mut GasMeter<T>,
gas_limit: Weight,
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 {
FrameArgs::Call{dest, cached_info} => {
let contract = if let Some(contract) = cached_info {
contract
} else {
<ContractInfoOf<T>>::get(&dest)
.ok_or((<Error<T>>::ContractNotFound.into(), 0))
.ok_or(<Error<T>>::ContractNotFound.into())
.and_then(|contract|
contract.get_alive()
.ok_or((<Error<T>>::ContractIsTombstone.into(), 0))
contract.get_alive().ok_or(<Error<T>>::ContractIsTombstone)
)?
};
let executable = E::from_storage(contract.code_hash, schedule, gas_meter)
.map_err(|e| (e.into(), 0))?;
let executable = E::from_storage(contract.code_hash, schedule, gas_meter)?;
// 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
@@ -705,9 +710,8 @@ where
// contract.
// See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324
let contract = Rent::<T, E>
::charge(&dest, contract, executable.occupied_storage())
.map_err(|e| (e.into(), executable.code_len()))?
.ok_or((Error::<T>::RentNotPaid.into(), executable.code_len()))?;
::charge(&dest, contract, executable.occupied_storage())?
.ok_or(Error::<T>::RentNotPaid)?;
(dest, contract, executable, ExportedFunction::Call)
}
FrameArgs::Instantiate{sender, trie_seed, executable, salt} => {
@@ -719,7 +723,7 @@ where
&account_id,
trie_id,
executable.code_hash().clone(),
).map_err(|e| (e.into(), executable.code_len()))?;
)?;
(account_id, contract, executable, ExportedFunction::Constructor)
}
};
@@ -732,8 +736,7 @@ where
contract_info: CachedContract::Cached(contract_info),
account_id,
entry_point,
nested_meter: gas_meter.nested(gas_limit)
.map_err(|e| (e.into(), executable.code_len()))?,
nested_meter: gas_meter.nested(gas_limit)?,
allows_reentry: true,
};
@@ -746,9 +749,9 @@ where
frame_args: FrameArgs<T, E>,
value_transferred: BalanceOf<T>,
gas_limit: Weight,
) -> Result<E, (ExecError, u32)> {
) -> Result<E, ExecError> {
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.
@@ -787,7 +790,7 @@ where
&mut self,
executable: E,
input_data: Vec<u8>
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
) -> Result<ExecReturnValue, ExecError> {
let entry_point = self.top_frame().entry_point;
let do_transaction = || {
// 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
// spawned. This is OK as overcharging is always safe.
let occupied_storage = executable.occupied_storage();
let code_len = executable.code_len();
// 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.
let output = executable.execute(
self,
&entry_point,
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.
if output.is_success() && entry_point == ExportedFunction::Constructor {
@@ -814,7 +816,7 @@ where
// It is not allowed to terminate a contract inside its constructor.
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
@@ -823,9 +825,8 @@ where
// in order to keep up the guarantuee that we always leave a tombstone behind
// with the exception of a contract that called `seal_terminate`.
let contract = Rent::<T, E>
::charge(&account_id, frame.invalidate(), occupied_storage)
.map_err(|e| (e.into(), code_len))?
.ok_or((Error::<T>::NewContractNotFunded.into(), code_len))?;
::charge(&account_id, frame.invalidate(), occupied_storage)?
.ok_or(Error::<T>::NewContractNotFunded)?;
frame.contract_info = CachedContract::Cached(contract);
// 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.
@@ -843,8 +844,8 @@ where
// comitted or rolled back when popping the frame.
let (success, output) = with_transaction(|| {
let output = do_transaction();
match output {
Ok((ref result, _)) if result.is_success() => {
match &output {
Ok(result) if result.is_success() => {
TransactionOutcome::Commit((true, output))
},
_ => TransactionOutcome::Rollback((false, output)),
@@ -1055,7 +1056,7 @@ where
value: BalanceOf<T>,
input_data: Vec<u8>,
allows_reentry: bool,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
) -> Result<ExecReturnValue, ExecError> {
// 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
// is caught by it.
@@ -1063,7 +1064,7 @@ where
let try_call = || {
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.
// Otherwise it would be possible to recursively call a contract from its own
@@ -1101,9 +1102,8 @@ where
endowment: BalanceOf<T>,
input_data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<T>, ExecReturnValue, u32), (ExecError, u32)> {
let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())
.map_err(|e| (e.into(), 0))?;
) -> Result<(AccountIdOf<T>, ExecReturnValue), ExecError> {
let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?;
let trie_seed = self.next_trie_seed();
let executable = self.push_frame(
FrameArgs::Instantiate {
@@ -1116,33 +1116,29 @@ where
gas_limit,
)?;
let account_id = self.top_frame().account_id.clone();
self.run(executable, input_data)
.map(|(ret, code_len)| (account_id, ret, code_len))
self.run(executable, input_data).map(|ret| (account_id, ret))
}
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)> {
fn terminate(&mut self, beneficiary: &AccountIdOf<Self::T>) -> Result<(), DispatchError> {
if self.is_recursive() {
return Err((Error::<T>::TerminatedWhileReentrant.into(), 0));
return Err(Error::<T>::TerminatedWhileReentrant.into());
}
let frame = self.top_frame_mut();
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(
true,
true,
&frame.account_id,
beneficiary,
T::Currency::free_balance(&frame.account_id),
).map_err(|e| (e, 0))?;
)?;
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(
Event::Terminated(frame.account_id.clone(), beneficiary.clone()),
);
Ok(code_len)
Ok(())
}
fn restore_to(
@@ -1151,30 +1147,33 @@ where
code_hash: CodeHash<Self::T>,
rent_allowance: BalanceOf<Self::T>,
delta: Vec<StorageKey>,
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
) -> Result<(), DispatchError> {
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(
&self.top_frame().account_id,
&account_id,
origin_contract,
dest.clone(),
code_hash.clone(),
rent_allowance,
delta,
&mut frame.nested_meter,
);
if let Ok(_) = result {
deposit_event::<Self::T>(
vec![],
Event::Restored(
self.top_frame().account_id.clone(),
account_id,
dest,
code_hash,
rent_allowance,
),
);
self.top_frame_mut().terminate();
frame.terminate();
}
result
}
@@ -1463,14 +1462,18 @@ mod tests {
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);
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);
0
Ok(())
}
fn execute<E: Ext<T = Test>>(
@@ -1597,7 +1600,7 @@ mod tests {
None,
).unwrap();
assert!(!output.0.is_success());
assert!(!output.is_success());
assert_eq!(get_balance(&origin), 100);
// the rent is still charged
@@ -1658,8 +1661,8 @@ mod tests {
);
let output = result.unwrap();
assert!(output.0.is_success());
assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4]));
assert!(output.is_success());
assert_eq!(output.data, Bytes(vec![1, 2, 3, 4]));
});
}
@@ -1689,8 +1692,8 @@ mod tests {
);
let output = result.unwrap();
assert!(!output.0.is_success());
assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4]));
assert!(!output.is_success());
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`.
assert_eq!(
r,
Err((Error::<Test>::MaxCallDepthReached.into(), 0))
Err(Error::<Test>::MaxCallDepthReached.into())
);
*reached_bottom = true;
} else {
@@ -2000,7 +2003,7 @@ mod tests {
let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
move |ctx, _| {
// 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,
dummy_ch,
Contracts::<Test>::subsistence_threshold() * 3,
@@ -2053,10 +2056,10 @@ mod tests {
vec![],
&[],
),
Err((ExecError {
Err(ExecError {
error: DispatchError::Other("It's a trap!"),
origin: ErrorOrigin::Callee,
}, 0))
})
);
exec_success()
@@ -2293,7 +2296,7 @@ mod tests {
assert_ne!(original_allowance, changed_allowance);
ctx.ext.set_rent_allowance(changed_allowance);
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()
);
assert_eq!(ctx.ext.rent_allowance(), changed_allowance);
@@ -2330,7 +2333,7 @@ mod tests {
let code = MockLoader::insert(Constructor, |ctx, _| {
assert_matches!(
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()
});
@@ -2426,7 +2429,7 @@ mod tests {
// call the contract passed as input with disabled reentry
let code_bob = MockLoader::insert(Call, |ctx, _| {
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, |_, _| {
@@ -2459,7 +2462,7 @@ mod tests {
0,
BOB.encode(),
None,
).map_err(|e| e.0.error),
).map_err(|e| e.error),
<Error<Test>>::ReentranceDenied,
);
});
@@ -2469,7 +2472,7 @@ mod tests {
fn call_deny_reentry() {
let code_bob = MockLoader::insert(Call, |ctx, _| {
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 {
exec_success()
}
@@ -2477,7 +2480,7 @@ mod tests {
// call BOB with input set to '1'
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(|| {
@@ -2495,7 +2498,7 @@ mod tests {
0,
vec![0],
None,
).map_err(|e| e.0.error),
).map_err(|e| e.error),
<Error<Test>>::ReentranceDenied,
);
});
-9
View File
@@ -167,15 +167,6 @@ where
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.
pub fn gas_spent(&self) -> Weight {
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 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.
#[pallet::weight(T::WeightInfo::call(T::Schedule::get().limits.code_len / 1024)
.saturating_add(*gas_limit)
)]
#[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))]
pub fn call(
origin: OriginFor<T>,
dest: <T::Lookup as StaticLookup>::Source,
@@ -289,13 +287,10 @@ pub mod pallet {
let dest = T::Lookup::lookup(dest)?;
let mut gas_meter = GasMeter::new(gas_limit);
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,
) {
Ok((output, len)) => (Ok(output), len),
Err((err, len)) => (Err(err), len),
};
gas_meter.into_dispatch_result(result, T::WeightInfo::call(code_len / 1024))
);
gas_meter.into_dispatch_result(result, T::WeightInfo::call())
}
/// 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
/// must be supplied.
#[pallet::weight(
T::WeightInfo::instantiate(
T::Schedule::get().limits.code_len / 1024, salt.len() as u32 / 1024
)
.saturating_add(*gas_limit)
T::WeightInfo::instantiate(salt.len() as u32 / 1024).saturating_add(*gas_limit)
)]
pub fn instantiate(
origin: OriginFor<T>,
@@ -374,13 +366,12 @@ pub mod pallet {
let mut gas_meter = GasMeter::new(gas_limit);
let schedule = T::Schedule::get();
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(
origin, executable, &mut gas_meter, &schedule, endowment, data, &salt, None,
).map(|(_address, output)| output);
gas_meter.into_dispatch_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(),
);
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(),
debug_message: debug_message.unwrap_or_default(),
}
+9 -13
View File
@@ -20,7 +20,7 @@
use crate::{
AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Pallet, Event,
TombstoneContractInfo, Config, CodeHash, Error,
storage::Storage, wasm::PrefabWasmModule, exec::Executable,
storage::Storage, wasm::PrefabWasmModule, exec::Executable, gas::GasMeter,
};
use sp_std::prelude::*;
use sp_io::hashing::blake2_256;
@@ -232,10 +232,6 @@ where
/// 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
/// deduct block will be set to the current block.
///
/// # Return Value
///
/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
pub fn restore_to(
origin: &T::AccountId,
mut origin_contract: AliveContractInfo<T>,
@@ -243,18 +239,19 @@ where
code_hash: CodeHash<T>,
rent_allowance: BalanceOf<T>,
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 current_block = <frame_system::Pallet<T>>::block_number();
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)
.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() {
Some(current_block)
@@ -263,7 +260,7 @@ where
};
// 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
// fail later due to tombstones not matching. This is because the restoration
@@ -287,13 +284,13 @@ where
);
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;
<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> {
code_hash,
rent_allowance,
@@ -306,8 +303,7 @@ where
let origin_free_balance = T::Currency::free_balance(&origin);
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
T::Currency::deposit_creating(&dest, origin_free_balance);
Ok((caller_code_len, tombstone_code_len))
Ok(())
}
/// 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`.
pub terminate: Weight,
/// Weight per byte of the terminated contract.
pub terminate_per_code_byte: Weight,
/// Weight of calling `seal_restore_to`.
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`.
pub restore_to_per_delta: Weight,
@@ -354,9 +345,6 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_call`.
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.
pub call_transfer_surcharge: Weight,
@@ -369,9 +357,6 @@ pub struct HostFnWeights<T: Config> {
/// Weight of calling `seal_instantiate`.
pub instantiate: Weight,
/// Weight per byte of the instantiated contract.
pub instantiate_per_code_byte: Weight,
/// Weight per input byte supplied to `seal_instantiate`.
pub instantiate_per_input_byte: Weight,
@@ -588,11 +573,8 @@ impl<T: Config> Default for HostFnWeights<T> {
r#return: cost!(seal_return),
return_per_byte: cost_byte!(seal_return_per_kb),
terminate: cost!(seal_terminate),
terminate_per_code_byte: cost_byte!(seal_terminate_per_code_kb),
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_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),
restore_to_per_delta: cost_batched!(seal_restore_to_per_delta),
random: cost_batched!(seal_random),
deposit_event: cost_batched!(seal_deposit_event),
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),
transfer: cost_batched!(seal_transfer),
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_code_transfer_input_output_kb, 0, 1, 0, 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_code_transfer_input_output_kb, 0, 0, 0, 1),
call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0),
call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0),
call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1),
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_code_input_output_salt_kb, 0, 1, 0, 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_code_input_output_salt_kb, 0, 0, 0, 1),
instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 1, 0, 0),
instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 1, 0),
instantiate_per_salt_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_salt_kb, 0, 0, 1),
hash_sha2_256: cost_batched!(seal_hash_sha2_256),
hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb),
hash_keccak_256: cost_batched!(seal_hash_keccak_256),
+63 -3
View File
@@ -363,7 +363,7 @@ where
fn calling_plain_account_fails() {
ExtBuilder::default().build().execute_with(|| {
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!(
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()),
@@ -1727,6 +1727,10 @@ fn self_destruct_works() {
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![
EventRecord {
phase: Phase::Initialization,
@@ -1738,7 +1742,7 @@ fn self_destruct_works() {
EventRecord {
phase: Phase::Initialization,
event: Event::Balances(
pallet_balances::Event::Transfer(addr.clone(), DJANGO, 93_086)
pallet_balances::Event::Transfer(addr.clone(), DJANGO, balance_after_rent)
),
topics: vec![],
},
@@ -1761,7 +1765,7 @@ fn self_destruct_works() {
// check that the beneficiary (django) got remaining balance
// 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);
});
}
#[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.
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
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
{
gas_meter.charge(CodeToken::UpdateRefcount(estimate_code_size::<T>(&code_hash)?))?;
<CodeStorage<T>>::mutate(code_hash, |existing| {
if let Some(module) = existing {
increment_64(&mut module.refcount);
Ok(module.original_code_len)
Ok(())
} else {
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.
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
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| {
if let Some(module) = existing {
let code_len = module.original_code_len;
module.refcount = module.refcount.saturating_sub(1);
if module.refcount == 0 {
*existing = None;
finish_removal::<T>(code_hash);
}
code_len
} else {
0
}
})
});
Ok(())
}
/// Load code with the given code hash.
@@ -120,13 +123,24 @@ where
/// If the module was instrumented with a lower version of schedule than
/// the current one given as an argument, then this function will perform
/// 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>(
code_hash: CodeHash<T>,
reinstrument: Option<(&Schedule<T>, &mut GasMeter<T>)>,
mut reinstrument: Option<(&Schedule<T>, &mut GasMeter<T>)>,
) -> Result<PrefabWasmModule<T>, DispatchError>
where
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)
.ok_or_else(|| Error::<T>::CodeNotFound)?;
prefab_module.code_hash = code_hash;
@@ -135,7 +149,7 @@ where
if prefab_module.instruction_weights_version < schedule.instruction_weights.version {
// The instruction weights have changed.
// 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)?;
}
}
@@ -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
/// a contract of the specified size in bytes.
/// Get the size of the instrumented code stored at `code_hash` without loading it.
///
/// 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))]
#[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 {
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);
}
fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError> {
code_cache::increment_refcount::<T>(code_hash)
fn add_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
-> Result<(), DispatchError>
{
code_cache::increment_refcount::<T>(code_hash, gas_meter)
}
fn remove_user(code_hash: CodeHash<T>) -> u32 {
code_cache::decrement_refcount::<T>(code_hash)
fn remove_user(code_hash: CodeHash<T>, gas_meter: &mut GasMeter<T>)
-> Result<(), DispatchError>
{
code_cache::decrement_refcount::<T>(code_hash, gas_meter)
}
fn execute<E: Ext<T = T>>(
@@ -349,14 +353,14 @@ mod tests {
value: u64,
data: Vec<u8>,
allows_reentry: bool,
) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
) -> Result<ExecReturnValue, ExecError> {
self.calls.push(CallEntry {
to,
value,
data,
allows_reentry,
});
Ok((ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() }, 0))
Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: call_return_data() })
}
fn instantiate(
&mut self,
@@ -365,7 +369,7 @@ mod tests {
endowment: u64,
data: Vec<u8>,
salt: &[u8],
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)> {
) -> Result<(AccountIdOf<Self::T>, ExecReturnValue), ExecError> {
self.instantiates.push(InstantiateEntry {
code_hash: code_hash.clone(),
endowment,
@@ -379,7 +383,6 @@ mod tests {
flags: ReturnFlags::empty(),
data: Bytes(Vec::new()),
},
0,
))
}
fn transfer(
@@ -396,11 +399,11 @@ mod tests {
fn terminate(
&mut self,
beneficiary: &AccountIdOf<Self::T>,
) -> Result<u32, (DispatchError, u32)> {
) -> Result<(), DispatchError> {
self.terminations.push(TerminationEntry {
beneficiary: beneficiary.clone(),
});
Ok(0)
Ok(())
}
fn restore_to(
&mut self,
@@ -408,14 +411,14 @@ mod tests {
code_hash: H256,
rent_allowance: u64,
delta: Vec<StorageKey>,
) -> Result<(u32, u32), (DispatchError, u32, u32)> {
) -> Result<(), DispatchError> {
self.restores.push(RestoreEntry {
dest,
code_hash,
rent_allowance,
delta,
});
Ok((0, 0))
Ok(())
}
fn get_storage(&mut self, key: &StorageKey) -> Option<Vec<u8>> {
self.storage.get(key).cloned()
@@ -616,7 +619,7 @@ mod tests {
fn contract_call_forward_input() {
const CODE: &str = r#"
(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 "env" "memory" (memory 1 1))
(func (export "call")
@@ -624,10 +627,8 @@ mod tests {
(call $seal_call
(i32.const 1) ;; Set FORWARD_INPUT bit
(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.
(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 4) ;; Length of input data buffer
(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() {
const CODE: &str = r#"
(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_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
@@ -687,10 +688,8 @@ mod tests {
(call $seal_call
(i32.const 11) ;; Set FORWARD_INPUT | CLONE_INPUT | ALLOW_REENTRY bits
(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.
(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 4) ;; Length of input data buffer
(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() {
const CODE: &str = r#"
(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))
(func (export "call")
(drop
(call $seal_call
(i32.const 5) ;; Set FORWARD_INPUT | TAIL_CALL bit
(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.
(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) ;; Length of input data buffer
(i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output
@@ -2000,25 +1997,18 @@ mod tests {
"#;
#[test]
fn contract_decode_failure() {
fn contract_decode_length_ignored() {
let mut mock_ext = MockExt::default();
let result = execute(
CODE_DECODE_FAILURE,
vec![],
&mut mock_ext,
);
assert_eq!(
result,
Err(ExecError {
error: Error::<Test>::DecodingFailed.into(),
origin: ErrorOrigin::Caller,
})
);
// AccountID implements `MaxEncodeLen` and therefore the supplied length is
// no longer needed nor used to determine how much is read from contract memory.
assert_ok!(result);
}
#[test]
#[cfg(feature = "unstable-interface")]
fn rent_params_work() {
+268 -195
View File
@@ -26,7 +26,7 @@ use crate::{
};
use bitflags::bitflags;
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 codec::{Decode, DecodeAll, Encode};
use sp_core::{Bytes, crypto::UncheckedFrom};
@@ -170,12 +170,8 @@ pub enum RuntimeCosts {
Return(u32),
/// Weight of calling `seal_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.
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.
Random,
/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
@@ -197,8 +193,6 @@ pub enum RuntimeCosts {
Transfer,
/// Weight of calling `seal_call` for the given input size.
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.
CallSurchargeTransfer,
/// 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
/// the existential deposit and is disregarded as corner case.
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.
InstantiateCopyOut(u32),
/// Weight of calling `seal_hash_sha_256` for the given input size.
@@ -221,8 +213,6 @@ pub enum RuntimeCosts {
HashBlake128(u32),
/// Weight charged by a chain extension through `seal_call_chain_extension`.
ChainExtension(u64),
/// Weight charged for copying data from the sandbox.
CopyIn(u32),
}
impl RuntimeCosts {
@@ -250,13 +240,8 @@ impl RuntimeCosts {
Return(len) => s.r#return
.saturating_add(s.return_per_byte.saturating_mul(len.into())),
Terminate => s.terminate,
TerminateSurchargeCodeSize(len) => s.terminate_per_code_byte.saturating_mul(len.into()),
RestoreTo(delta) => s.restore_to
.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,
DepositEvent{num_topic, len} => s.deposit_event
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
@@ -272,14 +257,11 @@ impl RuntimeCosts {
Transfer => s.transfer,
CallBase(len) => s.call
.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,
CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()),
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_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
.saturating_mul(len.into()),
HashSha256(len) => s.hash_sha2_256
@@ -291,7 +273,6 @@ impl RuntimeCosts {
HashBlake128(len) => s.hash_blake2_128
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
ChainExtension(amount) => amount,
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
};
RuntimeToken {
#[cfg(test)]
@@ -476,15 +457,6 @@ where
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.
///
/// 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())
}
/// 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.
///
/// Returns `Err` if one of the following conditions occurs:
@@ -520,25 +507,14 @@ where
///
/// # Note
///
/// It is safe to forgo benchmarking and charging weight relative to `len` for fixed
/// size types (basically everything not containing a heap collection):
/// Despite the fact that we are usually about to read the encoding of a fixed size
/// 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)
/// There must be an extra benchmark for determining the influence of `len` with
/// regard to the overall weight.
pub fn read_sandbox_memory_as_unbounded<D: Decode>(&self, ptr: u32, len: u32)
-> Result<D, DispatchError>
{
let amount = self.charge_gas(RuntimeCosts::CopyIn(len))?;
let buf = self.read_sandbox_memory(ptr, len)?;
let decoded = D::decode_all(&mut &buf[..])
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
self.ext.gas_meter().refund(amount);
Ok(decoded)
}
@@ -575,7 +551,7 @@ where
}
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 {
Err(Error::<E::T>::OutputBufferTooSmall)?
@@ -675,19 +651,18 @@ where
&mut self,
flags: CallFlags,
callee_ptr: u32,
callee_len: u32,
gas: u64,
value_ptr: u32,
value_len: u32,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
output_len_ptr: u32
) -> Result<ReturnCode, TrapReason> {
) -> Result<ReturnCode, TrapReason>
{
self.charge_gas(RuntimeCosts::CallBase(input_data_len))?;
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
self.read_sandbox_memory_as(callee_ptr, callee_len)?;
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr, value_len)?;
self.read_sandbox_memory_as(callee_ptr)?;
let value: BalanceOf<<E as Ext>::T> = self.read_sandbox_memory_as(value_ptr)?;
let input_data = if flags.contains(CallFlags::CLONE_INPUT) {
self.input_data.as_ref().ok_or_else(|| Error::<E::T>::InputForwarded)?.clone()
} else if flags.contains(CallFlags::FORWARD_INPUT) {
@@ -698,23 +673,15 @@ where
if value > 0u32.into() {
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 call_outcome = ext.call(
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
// a halt anyways without anymore code being executed.
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 {
flags: return_value.flags.bits(),
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| {
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(
ctx,
account_ptr: u32,
account_len: u32,
_account_len: u32,
value_ptr: u32,
value_len: u32
_value_len: u32
) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::Transfer)?;
let callee: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(account_ptr, account_len)?;
ctx.read_sandbox_memory_as(account_ptr)?;
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);
match result {
@@ -860,15 +913,23 @@ define_env!(Env, <E: Ext>,
// Make a call to another contract.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function with
// `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(
ctx,
callee_ptr: u32,
callee_len: u32,
_callee_len: u32,
gas: u64,
value_ptr: u32,
value_len: u32,
_value_len: u32,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
@@ -877,10 +938,8 @@ define_env!(Env, <E: Ext>,
ctx.call(
CallFlags::ALLOW_REENTRY,
callee_ptr,
callee_len,
gas,
value_ptr,
value_len,
input_data_ptr,
input_data_len,
output_ptr,
@@ -899,11 +958,9 @@ define_env!(Env, <E: Ext>,
// - flags: See [`CallFlags`] for a documenation of the supported flags.
// - callee_ptr: a pointer to the address of the callee contract.
// 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.
// - value_ptr: a pointer to the buffer with value, how much value to send.
// 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_len: length of the input data buffer.
// - output_ptr: a pointer where the output buffer is copied to.
@@ -924,10 +981,8 @@ define_env!(Env, <E: Ext>,
ctx,
flags: u32,
callee_ptr: u32,
callee_len: u32,
gas: u64,
value_ptr: u32,
value_len: u32,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
@@ -936,10 +991,8 @@ define_env!(Env, <E: Ext>,
ctx.call(
CallFlags::from_bits(flags).ok_or_else(|| "used rerved bit in CallFlags")?,
callee_ptr,
callee_len,
gas,
value_ptr,
value_len,
input_data_ptr,
input_data_len,
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.
//
// This function creates an account and executes the constructor defined in the code specified
@@ -962,11 +1058,9 @@ define_env!(Env, <E: Ext>,
// # Parameters
//
// - 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.
// - value_ptr: a pointer to the buffer with value, how much value to send.
// 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_len: length of the input data buffer.
// - 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::NewContractNotFunded`
// `ReturnCode::CodeNotFound`
[seal0] seal_instantiate(
[seal1] 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,
@@ -1008,37 +1100,35 @@ define_env!(Env, <E: Ext>,
salt_ptr: u32,
salt_len: u32
) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::InstantiateBase {input_data_len, salt_len})?;
let code_hash: CodeHash<<E as Ext>::T> =
ctx.read_sandbox_memory_as(code_hash_ptr, code_hash_len)?;
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr, value_len)?;
let input_data = ctx.read_sandbox_memory(input_data_ptr, input_data_len)?;
let salt = ctx.read_sandbox_memory(salt_ptr, salt_len)?;
let charged = ctx.charge_gas(
RuntimeCosts::InstantiateSurchargeCodeSize(
<E::T as Config>::Schedule::get().limits.code_len
)
)?;
let ext = &mut ctx.ext;
let instantiate_outcome = ext.instantiate(gas, code_hash, value, input_data, &salt);
let code_len = match &instantiate_outcome {
Ok((_, _, code_len)) => code_len,
Err((_, code_len)) => code_len,
};
ctx.adjust_gas(charged, RuntimeCosts::InstantiateSurchargeCodeSize(*code_len));
if let Ok((address, output, _)) = &instantiate_outcome {
if !output.flags.contains(ReturnFlags::REVERT) {
ctx.write_sandbox_output(
address_ptr, address_len_ptr, &address.encode(), true, already_charged,
)?;
}
ctx.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).map_err(|(err, _)| err)
)?)
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,
)
},
// Remove the calling account and transfer remaining balance.
//
// # Deprecation
//
// This is equivalent to calling the newer version of this function. The newer version
// drops the now unnecessary length fields.
//
// # Note
//
// The value `_beneficiary_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_terminate(ctx, beneficiary_ptr: u32, _beneficiary_len: u32) => {
ctx.terminate(beneficiary_ptr)
},
// 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
// where all remaining funds of the caller are transferred.
// Should be decodable as an `T::AccountId`. Traps otherwise.
// - beneficiary_len: length of the address buffer.
//
// # Traps
//
// - The contract is live i.e is already on the call stack.
// - Failed to send the balance to the beneficiary.
// - The deletion queue is full.
[seal0] seal_terminate(
ctx,
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)
[seal1] seal_terminate(ctx, beneficiary_ptr: u32) => {
ctx.terminate(beneficiary_ptr)
},
// 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.
//
// 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
// 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.
// - `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.
// - `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.
// - `delta_ptr` is the pointer to the start of a buffer that has `delta_count` storage keys
// laid out sequentially.
@@ -1354,67 +1457,21 @@ define_env!(Env, <E: Ext>,
// - Tombstone hashes do not match.
// - The calling contract is already present on the call stack.
// - The supplied code_hash does not exist on-chain.
[seal0] seal_restore_to(
[seal1] 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.charge_gas(RuntimeCosts::RestoreTo(delta_count))?;
let dest: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(dest_ptr, dest_len)?;
let code_hash: CodeHash<<E as Ext>::T> =
ctx.read_sandbox_memory_as(code_hash_ptr, code_hash_len)?;
let rent_allowance: BalanceOf<<E as Ext>::T> =
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)
ctx.restore_to(
dest_ptr,
code_hash_ptr,
rent_allowance_ptr,
delta_ptr,
delta_count,
)
},
// 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 {
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.
@@ -1482,17 +1539,33 @@ define_env!(Env, <E: Ext>,
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
// Should be decodable as a `T::Balance`. Traps otherwise.
// - value_len: length of the value buffer.
[seal0] seal_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
[seal1] seal_set_rent_allowance(ctx, value_ptr: u32) => {
ctx.charge_gas(RuntimeCosts::SetRentAllowance)?;
let value: BalanceOf<<E as Ext>::T> =
ctx.read_sandbox_memory_as(value_ptr, value_len)?;
let value: BalanceOf<<E as Ext>::T> = ctx.read_sandbox_memory_as(value_ptr)?;
ctx.ext.set_rent_allowance(value);
Ok(())
},
File diff suppressed because it is too large Load Diff