mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 19:01:08 +00:00
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:
committed by
GitHub
parent
bc0520913d
commit
0cccd282a1
@@ -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`.
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user