Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit eae82ab

Browse files
atheiapopiakgui1117
authored
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 <[email protected]> * Add v1 for seal_set_rent_allowance * Remove unneeded trait bound Co-authored-by: Guillaume Thiolliere <[email protected]> Co-authored-by: Alexander Popiak <[email protected]> Co-authored-by: Guillaume Thiolliere <[email protected]>
1 parent df50122 commit eae82ab

18 files changed

+1238
-1132
lines changed

frame/contracts/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ output to an RPC client.
4242
- Make storage and fields of `Schedule` private to the crate.
4343
[#8359](https://github.com/paritytech/substrate/pull/8359)
4444

45+
### Fixed
46+
47+
- Remove pre-charging which caused wrongly estimated weights
48+
[#8976](https://github.com/paritytech/substrate/pull/8976)
49+
4550
## [v3.0.0] 2021-02-25
4651

4752
This version constitutes the first release that brings any stability guarantees (see above).

frame/contracts/fixtures/dummy.wat

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
;; A valid contract which does nothing at all
2+
(module
3+
(func (export "deploy"))
4+
(func (export "call"))
5+
)

frame/contracts/fixtures/instantiate_return_code.wat

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
;; The rest of the input is forwarded to the constructor of the callee
55
(module
66
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
7-
(import "seal0" "seal_instantiate" (func $seal_instantiate
8-
(param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
7+
(import "seal1" "seal_instantiate" (func $seal_instantiate
8+
(param i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
99
))
1010
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
1111
(import "env" "memory" (memory 1 1))
@@ -29,10 +29,8 @@
2929
(i32.const 8)
3030
(call $seal_instantiate
3131
(i32.const 16) ;; Pointer to the code hash.
32-
(i32.const 32) ;; Length of the code hash.
3332
(i64.const 0) ;; How much gas to devote for the execution. 0 = all.
3433
(i32.const 0) ;; Pointer to the buffer with value to transfer
35-
(i32.const 8) ;; Length of the buffer with value to transfer.
3634
(i32.const 48) ;; Pointer to input data buffer address
3735
(i32.const 4) ;; Length of input data buffer
3836
(i32.const 0xffffffff) ;; u32 max sentinel value: do not copy address

frame/contracts/fixtures/ok_trap_revert.wat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@
3232
;; 2 = trap
3333
(unreachable)
3434
)
35-
)
35+
)

frame/contracts/fixtures/restoration.wat

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
(module
22
(import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
33
(import "seal0" "seal_input" (func $seal_input (param i32 i32)))
4-
(import "seal0" "seal_restore_to"
4+
(import "seal1" "seal_restore_to"
55
(func $seal_restore_to
6-
(param i32 i32 i32 i32 i32 i32 i32 i32)
6+
(param i32 i32 i32 i32 i32)
77
)
88
)
99
(import "env" "memory" (memory 1 1))
@@ -27,15 +27,12 @@
2727
)
2828
)
2929
(call $seal_restore_to
30-
;; Pointer and length of the encoded dest buffer.
30+
;; Pointer to the encoded dest buffer.
3131
(i32.const 340)
32-
(i32.const 32)
33-
;; Pointer and length of the encoded code hash buffer
32+
;; Pointer to the encoded code hash buffer
3433
(i32.const 308)
35-
(i32.const 32)
36-
;; Pointer and length of the encoded rent_allowance buffer
34+
;; Pointer to the encoded rent_allowance buffer
3735
(i32.const 296)
38-
(i32.const 8)
3936
;; Pointer and number of items in the delta buffer.
4037
;; This buffer specifies multiple keys for removal before restoration.
4138
(i32.const 100)

frame/contracts/src/benchmarking/code.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,14 @@ where
258258

259259
/// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
260260
pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
261+
// We want the module to have the size `dummy_bytes`.
262+
// This is not completely correct as the overhead grows when the contract grows
263+
// because of variable length integer encoding. However, it is good enough to be that
264+
// close for benchmarking purposes.
265+
let module_overhead = 65;
261266
ModuleDefinition {
262267
memory: Some(ImportedMemory::max::<T>()),
263-
dummy_section: dummy_bytes,
268+
dummy_section: dummy_bytes.saturating_sub(module_overhead),
264269
.. Default::default()
265270
}
266271
.into()

frame/contracts/src/benchmarking/mod.rs

Lines changed: 29 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,25 @@ benchmarks! {
320320
Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
321321
}
322322

323+
// The weight of loading and decoding of a contract's code per kilobyte.
324+
code_load {
325+
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
326+
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
327+
Contracts::<T>::store_code_raw(code)?;
328+
}: {
329+
<PrefabWasmModule<T>>::from_storage_noinstr(hash)?;
330+
}
331+
332+
// The weight of changing the refcount of a contract's code per kilobyte.
333+
code_refcount {
334+
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
335+
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
336+
Contracts::<T>::store_code_raw(code)?;
337+
let mut gas_meter = GasMeter::new(Weight::max_value());
338+
}: {
339+
<PrefabWasmModule<T>>::add_user(hash, &mut gas_meter)?;
340+
}
341+
323342
// This constructs a contract that is maximal expensive to instrument.
324343
// It creates a maximum number of metering blocks per byte.
325344
// The size of the salt influences the runtime because is is hashed in order to
@@ -352,16 +371,14 @@ benchmarks! {
352371
}
353372

354373
// Instantiate uses a dummy contract constructor to measure the overhead of the instantiate.
355-
// `c`: Size of the code in kilobytes.
356374
// `s`: Size of the salt in kilobytes.
357375
instantiate {
358-
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
359376
let s in 0 .. code::max_pages::<T>() * 64;
360377
let salt = vec![42u8; (s * 1024) as usize];
361378
let endowment = caller_funding::<T>() / 3u32.into();
362379
let caller = whitelisted_caller();
363380
T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
364-
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
381+
let WasmModule { code, hash, .. } = WasmModule::<T>::dummy();
365382
let origin = RawOrigin::Signed(caller.clone());
366383
let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
367384
Contracts::<T>::store_code_raw(code)?;
@@ -380,12 +397,10 @@ benchmarks! {
380397
// won't call `seal_input` in its constructor to copy the data to contract memory.
381398
// The dummy contract used here does not do this. The costs for the data copy is billed as
382399
// part of `seal_input`.
383-
// `c`: Size of the code in kilobytes.
384400
call {
385-
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
386401
let data = vec![42u8; 1024];
387402
let instance = Contract::<T>::with_caller(
388-
whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), vec![], Endow::CollectRent
403+
whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent
389404
)?;
390405
let value = T::Currency::minimum_balance() * 100u32.into();
391406
let origin = RawOrigin::Signed(instance.caller.clone());
@@ -720,43 +735,6 @@ benchmarks! {
720735
}
721736
}
722737

723-
seal_terminate_per_code_kb {
724-
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
725-
let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
726-
let beneficiary_bytes = beneficiary.encode();
727-
let beneficiary_len = beneficiary_bytes.len();
728-
let code = WasmModule::<T>::from(ModuleDefinition {
729-
memory: Some(ImportedMemory::max::<T>()),
730-
imported_functions: vec![ImportedFunction {
731-
module: "seal0",
732-
name: "seal_terminate",
733-
params: vec![ValueType::I32, ValueType::I32],
734-
return_type: None,
735-
}],
736-
data_segments: vec![
737-
DataSegment {
738-
offset: 0,
739-
value: beneficiary_bytes,
740-
},
741-
],
742-
call_body: Some(body::repeated(1, &[
743-
Instruction::I32Const(0), // beneficiary_ptr
744-
Instruction::I32Const(beneficiary_len as i32), // beneficiary_len
745-
Instruction::Call(0),
746-
])),
747-
dummy_section: c * 1024,
748-
.. Default::default()
749-
});
750-
let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
751-
let origin = RawOrigin::Signed(instance.caller.clone());
752-
assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
753-
assert_eq!(T::Currency::total_balance(&instance.account_id), Endow::max::<T>());
754-
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
755-
verify {
756-
assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
757-
assert_eq!(T::Currency::total_balance(&beneficiary), Endow::max::<T>());
758-
}
759-
760738
seal_restore_to {
761739
let r in 0 .. 1;
762740

@@ -836,18 +814,15 @@ benchmarks! {
836814
}
837815
}
838816

839-
// `c`: Code size of caller contract
840-
// `t`: Code size of tombstone contract
841817
// `d`: Number of supplied delta keys
842-
seal_restore_to_per_code_kb_delta {
843-
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
844-
let t in 0 .. T::Schedule::get().limits.code_len / 1024;
818+
seal_restore_to_per_delta {
845819
let d in 0 .. API_BENCHMARK_BATCHES;
846-
let mut tombstone = ContractWithStorage::<T>::with_code(
847-
WasmModule::<T>::dummy_with_bytes(t * 1024), 0, 0
848-
)?;
820+
let mut tombstone = ContractWithStorage::<T>::new(0, 0)?;
849821
tombstone.evict()?;
850-
let delta = create_storage::<T>(d * API_BENCHMARK_BATCH_SIZE, T::Schedule::get().limits.payload_len)?;
822+
let delta = create_storage::<T>(
823+
d * API_BENCHMARK_BATCH_SIZE,
824+
T::Schedule::get().limits.payload_len,
825+
)?;
851826

852827
let dest = tombstone.contract.account_id.encode();
853828
let dest_len = dest.len();
@@ -909,7 +884,6 @@ benchmarks! {
909884
Instruction::Call(0),
910885
Instruction::End,
911886
])),
912-
dummy_section: c * 1024,
913887
.. Default::default()
914888
});
915889

@@ -1393,8 +1367,7 @@ benchmarks! {
13931367
let origin = RawOrigin::Signed(instance.caller.clone());
13941368
}: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
13951369

1396-
seal_call_per_code_transfer_input_output_kb {
1397-
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
1370+
seal_call_per_transfer_input_output_kb {
13981371
let t in 0 .. 1;
13991372
let i in 0 .. code::max_pages::<T>() * 64;
14001373
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1417,7 +1390,6 @@ benchmarks! {
14171390
Instruction::Call(0),
14181391
Instruction::End,
14191392
])),
1420-
dummy_section: c * 1024,
14211393
.. Default::default()
14221394
});
14231395
let callees = (0..API_BENCHMARK_BATCH_SIZE)
@@ -1593,8 +1565,7 @@ benchmarks! {
15931565
}
15941566
}
15951567

1596-
seal_instantiate_per_code_input_output_salt_kb {
1597-
let c in 0 .. T::Schedule::get().limits.code_len / 1024;
1568+
seal_instantiate_per_input_output_salt_kb {
15981569
let i in 0 .. (code::max_pages::<T>() - 1) * 64;
15991570
let o in 0 .. (code::max_pages::<T>() - 1) * 64;
16001571
let s in 0 .. (code::max_pages::<T>() - 1) * 64;
@@ -1617,7 +1588,6 @@ benchmarks! {
16171588
Instruction::Call(0),
16181589
Instruction::End,
16191590
])),
1620-
dummy_section: c * 1024,
16211591
.. Default::default()
16221592
});
16231593
let hash = callee_code.hash.clone();

frame/contracts/src/chain_extension.rs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ use crate::{
5959
wasm::{Runtime, RuntimeCosts},
6060
};
6161
use codec::Decode;
62-
use frame_support::weights::Weight;
62+
use frame_support::{weights::Weight, traits::MaxEncodedLen};
6363
use sp_runtime::DispatchError;
6464
use sp_std::{
6565
marker::PhantomData,
@@ -300,18 +300,21 @@ where
300300
Ok(())
301301
}
302302

303-
/// Reads `in_len` from contract memory and scale decodes it.
303+
/// Reads and decodes a type with a size fixed at compile time from contract memory.
304304
///
305305
/// This function is secure and recommended for all input types of fixed size
306306
/// as long as the cost of reading the memory is included in the overall already charged
307307
/// weight of the chain extension. This should usually be the case when fixed input types
308-
/// are used. Non fixed size types (like everything using `Vec`) usually need to use
309-
/// [`in_len()`](Self::in_len) in order to properly charge the necessary weight.
310-
pub fn read_as<T: Decode>(&mut self) -> Result<T> {
311-
self.inner.runtime.read_sandbox_memory_as(
312-
self.inner.input_ptr,
313-
self.inner.input_len,
314-
)
308+
/// are used.
309+
pub fn read_as<T: Decode + MaxEncodedLen>(&mut self) -> Result<T> {
310+
self.inner.runtime.read_sandbox_memory_as(self.inner.input_ptr)
311+
}
312+
313+
/// Reads and decodes a type with a dynamic size from contract memory.
314+
///
315+
/// Make sure to include `len` in your weight calculations.
316+
pub fn read_as_unbounded<T: Decode>(&mut self, len: u32) -> Result<T> {
317+
self.inner.runtime.read_sandbox_memory_as_unbounded(self.inner.input_ptr, len)
315318
}
316319

317320
/// The length of the input as passed in as `input_len`.

0 commit comments

Comments
 (0)