pallet_contracts/benchmarking/
call_builder.rs1use 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
35pub 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
48pub 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 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 benchmarking::add_to_whitelist(
88 frame_system::Account::<T>::hashed_key_for(&contract.account_id).into(),
89 );
90
91 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 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 pub fn set_origin(&mut self, origin: Origin<T>) {
119 self.origin = origin;
120 }
121
122 pub fn set_balance(&mut self, value: BalanceOf<T>) {
124 self.contract.set_balance(value);
125 }
126
127 pub fn set_data(&mut self, value: Vec<u8>) {
129 self.data = value;
130 }
131
132 pub fn set_transient_storage_size(&mut self, size: u32) {
134 self.transient_storage_size = size;
135 }
136
137 pub fn enable_debug_message(&mut self) {
139 self.debug_message = Some(Default::default());
140 }
141
142 pub fn debug_message(&self) -> Option<DebugBufferVec<T>> {
144 self.debug_message.clone()
145 }
146
147 pub fn data(&self) -> Vec<u8> {
149 self.data.clone()
150 }
151
152 pub fn contract(&self) -> Contract<T> {
154 self.contract.clone()
155 }
156
157 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 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 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 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);