referrerpolicy=no-referrer-when-downgrade

pallet_contracts/benchmarking/
call_builder.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use crate::{
19	benchmarking::{Contract, WasmModule},
20	exec::{Ext, Key, Stack},
21	storage::meter::Meter,
22	transient_storage::MeterEntry,
23	wasm::Runtime,
24	BalanceOf, Config, DebugBufferVec, Determinism, Error, ExecReturnValue, GasMeter, Origin,
25	Schedule, TypeInfo, WasmBlob, Weight,
26};
27use alloc::{vec, vec::Vec};
28use codec::{Encode, HasCompact};
29use core::fmt::Debug;
30use frame_benchmarking::benchmarking;
31use sp_core::Get;
32
33type StackExt<'a, T> = Stack<'a, T, WasmBlob<T>>;
34
35/// A prepared contract call ready to be executed.
36pub struct PreparedCall<'a, T: Config> {
37	func: wasmi::Func,
38	store: wasmi::Store<Runtime<'a, StackExt<'a, T>>>,
39}
40
41impl<'a, T: Config> PreparedCall<'a, T> {
42	pub fn call(mut self) -> ExecReturnValue {
43		let result = self.func.call(&mut self.store, &[], &mut []);
44		WasmBlob::<T>::process_result(self.store, result).unwrap()
45	}
46}
47
48/// A builder used to prepare a contract call.
49pub struct CallSetup<T: Config> {
50	contract: Contract<T>,
51	dest: T::AccountId,
52	origin: Origin<T>,
53	gas_meter: GasMeter<T>,
54	storage_meter: Meter<T>,
55	schedule: Schedule<T>,
56	value: BalanceOf<T>,
57	debug_message: Option<DebugBufferVec<T>>,
58	determinism: Determinism,
59	data: Vec<u8>,
60	transient_storage_size: u32,
61}
62
63impl<T> Default for CallSetup<T>
64where
65	T: Config + pallet_balances::Config,
66	<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
67{
68	fn default() -> Self {
69		Self::new(WasmModule::dummy())
70	}
71}
72
73impl<T> CallSetup<T>
74where
75	T: Config + pallet_balances::Config,
76	<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
77{
78	/// Setup a new call for the given module.
79	pub fn new(module: WasmModule<T>) -> Self {
80		let contract = Contract::<T>::new(module.clone(), vec![]).unwrap();
81		let dest = contract.account_id.clone();
82		let origin = Origin::from_account_id(contract.caller.clone());
83
84		let storage_meter = Meter::new(&origin, None, 0u32.into()).unwrap();
85
86		// Whitelist contract account, as it is already accounted for in the call benchmark
87		benchmarking::add_to_whitelist(
88			frame_system::Account::<T>::hashed_key_for(&contract.account_id).into(),
89		);
90
91		// Whitelist the contract's contractInfo as it is already accounted for in the call
92		// benchmark
93		benchmarking::add_to_whitelist(
94			crate::ContractInfoOf::<T>::hashed_key_for(&contract.account_id).into(),
95		);
96
97		Self {
98			contract,
99			dest,
100			origin,
101			gas_meter: GasMeter::new(Weight::MAX),
102			storage_meter,
103			schedule: T::Schedule::get(),
104			value: 0u32.into(),
105			debug_message: None,
106			determinism: Determinism::Enforced,
107			data: vec![],
108			transient_storage_size: 0,
109		}
110	}
111
112	/// Set the meter's storage deposit limit.
113	pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf<T>) {
114		self.storage_meter = Meter::new(&self.origin, Some(balance), 0u32.into()).unwrap();
115	}
116
117	/// Set the call's origin.
118	pub fn set_origin(&mut self, origin: Origin<T>) {
119		self.origin = origin;
120	}
121
122	/// Set the contract's balance.
123	pub fn set_balance(&mut self, value: BalanceOf<T>) {
124		self.contract.set_balance(value);
125	}
126
127	/// Set the call's input data.
128	pub fn set_data(&mut self, value: Vec<u8>) {
129		self.data = value;
130	}
131
132	/// Set the transient storage size.
133	pub fn set_transient_storage_size(&mut self, size: u32) {
134		self.transient_storage_size = size;
135	}
136
137	/// Set the debug message.
138	pub fn enable_debug_message(&mut self) {
139		self.debug_message = Some(Default::default());
140	}
141
142	/// Get the debug message.
143	pub fn debug_message(&self) -> Option<DebugBufferVec<T>> {
144		self.debug_message.clone()
145	}
146
147	/// Get the call's input data.
148	pub fn data(&self) -> Vec<u8> {
149		self.data.clone()
150	}
151
152	/// Get the call's contract.
153	pub fn contract(&self) -> Contract<T> {
154		self.contract.clone()
155	}
156
157	/// Build the call stack.
158	pub fn ext(&mut self) -> (StackExt<'_, T>, WasmBlob<T>) {
159		let mut ext = StackExt::bench_new_call(
160			self.dest.clone(),
161			self.origin.clone(),
162			&mut self.gas_meter,
163			&mut self.storage_meter,
164			&self.schedule,
165			self.value,
166			self.debug_message.as_mut(),
167			self.determinism,
168		);
169		if self.transient_storage_size > 0 {
170			Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
171		}
172		ext
173	}
174
175	/// Prepare a call to the module.
176	pub fn prepare_call<'a>(
177		ext: &'a mut StackExt<'a, T>,
178		module: WasmBlob<T>,
179		input: Vec<u8>,
180	) -> PreparedCall<'a, T> {
181		let (func, store) = module.bench_prepare_call(ext, input);
182		PreparedCall { func, store }
183	}
184
185	/// Add transient_storage
186	fn with_transient_storage(ext: &mut StackExt<T>, size: u32) -> Result<(), &'static str> {
187		let &MeterEntry { amount, limit } = ext.transient_storage().meter().current();
188		ext.transient_storage().meter().current_mut().limit = size;
189		for i in 1u32.. {
190			let mut key_data = i.to_le_bytes().to_vec();
191			while key_data.last() == Some(&0) {
192				key_data.pop();
193			}
194			let key = Key::<T>::try_from_var(key_data).unwrap();
195			if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) {
196				// Restore previous settings.
197				ext.transient_storage().meter().current_mut().limit = limit;
198				ext.transient_storage().meter().current_mut().amount = amount;
199				if e == Error::<T>::OutOfTransientStorage.into() {
200					break;
201				} else {
202					return Err("Initialization of the transient storage failed");
203				}
204			}
205		}
206		Ok(())
207	}
208}
209
210#[macro_export]
211macro_rules! memory(
212	($($bytes:expr,)*) => {
213		 vec![]
214		    .into_iter()
215		    $(.chain($bytes))*
216		    .collect::<Vec<_>>()
217	};
218);
219
220#[macro_export]
221macro_rules! build_runtime(
222	($runtime:ident, $memory:ident: [$($segment:expr,)*]) => {
223		$crate::build_runtime!($runtime, _contract, $memory: [$($segment,)*]);
224	};
225	($runtime:ident, $contract:ident, $memory:ident: [$($bytes:expr,)*]) => {
226		$crate::build_runtime!($runtime, $contract);
227		let mut $memory = $crate::memory!($($bytes,)*);
228	};
229	($runtime:ident, $contract:ident) => {
230		let mut setup = CallSetup::<T>::default();
231		let $contract = setup.contract();
232		let input = setup.data();
233		let (mut ext, _) = setup.ext();
234		let mut $runtime = crate::wasm::Runtime::new(&mut ext, input);
235	};
236);