1#![cfg(any(feature = "runtime-benchmarks", test))]
22#![cfg_attr(test, allow(dead_code))]
27
28use crate::{
29 AccountInfo, BalanceOf, BalanceWithDust, Code, CodeInfoOf, Config, ContractBlob, ContractInfo,
30 Error, ExecConfig, ExecOrigin as Origin, OriginFor, Pallet as Contracts, PristineCode, Weight,
31 address::AddressMapper,
32 exec::{ExportedFunction, Key, PrecompileExt, Stack},
33 limits,
34 metering::{TransactionLimits, TransactionMeter},
35 transient_storage::MeterEntry,
36 vm::pvm::{PreparedCall, Runtime},
37};
38use alloc::{vec, vec::Vec};
39use frame_support::{storage::child, traits::fungible::Mutate};
40use frame_system::RawOrigin;
41use pallet_revive_fixtures::bench as bench_fixtures;
42use sp_core::{H160, H256, U256};
43use sp_io::hashing::keccak_256;
44use sp_runtime::traits::{Bounded, Hash};
45
46type StackExt<'a, T> = Stack<'a, T, ContractBlob<T>>;
47
48pub struct CallSetup<T: Config> {
50 contract: Contract<T>,
51 dest: T::AccountId,
52 origin: Origin<T>,
53 transaction_meter: TransactionMeter<T>,
54 value: BalanceOf<T>,
55 data: Vec<u8>,
56 transient_storage_size: u32,
57 exec_config: ExecConfig<T>,
58 read_only: bool,
59 delegate_call: bool,
60}
61
62impl<T> Default for CallSetup<T>
63where
64 T: Config,
65{
66 fn default() -> Self {
67 Self::new(VmBinaryModule::dummy())
68 }
69}
70
71impl<T> CallSetup<T>
72where
73 T: Config,
74{
75 pub fn new(module: VmBinaryModule) -> Self {
77 let contract = Contract::<T>::new(module, vec![]).unwrap();
78 let dest = contract.account_id.clone();
79 let origin = Origin::from_account_id(contract.caller.clone());
80
81 #[cfg(feature = "runtime-benchmarks")]
82 {
83 frame_benchmarking::benchmarking::add_to_whitelist(
85 frame_system::Account::<T>::hashed_key_for(&contract.account_id).into(),
86 );
87
88 frame_benchmarking::benchmarking::add_to_whitelist(
91 crate::AccountInfoOf::<T>::hashed_key_for(&T::AddressMapper::to_address(
92 &contract.account_id,
93 ))
94 .into(),
95 );
96 }
97
98 Self {
99 contract,
100 dest,
101 origin,
102 transaction_meter: TransactionMeter::new(TransactionLimits::WeightAndDeposit {
103 weight_limit: Weight::MAX,
104 deposit_limit: default_deposit_limit::<T>(),
105 })
106 .unwrap(),
107 value: 0u32.into(),
108 data: vec![],
109 transient_storage_size: 0,
110 exec_config: ExecConfig::new_substrate_tx(),
111 read_only: false,
112 delegate_call: false,
113 }
114 }
115
116 pub fn set_storage_deposit_limit(&mut self, balance: BalanceOf<T>) {
118 self.transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit {
119 weight_limit: Weight::MAX,
120 deposit_limit: balance,
121 })
122 .unwrap();
123 }
124
125 pub fn set_origin(&mut self, origin: Origin<T>) {
127 self.origin = origin;
128 }
129
130 pub fn set_balance(&mut self, value: impl Into<BalanceWithDust<BalanceOf<T>>>) {
132 self.contract.set_balance(value);
133 }
134
135 pub fn set_data(&mut self, value: Vec<u8>) {
137 self.data = value;
138 }
139
140 pub fn set_transient_storage_size(&mut self, size: u32) {
142 self.transient_storage_size = size;
143 }
144
145 pub fn set_read_only(&mut self, read_only: bool) {
147 self.read_only = read_only;
148 }
149
150 pub fn set_delegate_call(&mut self, delegate: bool) {
152 self.delegate_call = delegate;
153 }
154
155 pub fn data(&self) -> Vec<u8> {
157 self.data.clone()
158 }
159
160 pub fn contract(&self) -> Contract<T> {
162 self.contract.clone()
163 }
164
165 pub fn ext(&mut self) -> (StackExt<'_, T>, ContractBlob<T>) {
167 let mut ext = StackExt::bench_new_call(
168 T::AddressMapper::to_address(&self.dest),
169 self.origin.clone(),
170 &mut self.transaction_meter,
171 self.value,
172 &self.exec_config,
173 self.read_only,
174 self.delegate_call,
175 );
176 if self.transient_storage_size > 0 {
177 Self::with_transient_storage(&mut ext.0, self.transient_storage_size).unwrap();
178 }
179 ext
180 }
181
182 pub fn prepare_call<'a>(
184 ext: &'a mut StackExt<'a, T>,
185 module: ContractBlob<T>,
186 input: Vec<u8>,
187 aux_data_size: u32,
188 ) -> PreparedCall<'a, StackExt<'a, T>> {
189 module
190 .prepare_call(Runtime::new(ext, input), ExportedFunction::Call, aux_data_size)
191 .unwrap()
192 }
193
194 fn with_transient_storage(ext: &mut StackExt<T>, size: u32) -> Result<(), &'static str> {
196 let &MeterEntry { amount, limit } = ext.transient_storage().meter().current();
197 ext.transient_storage().meter().current_mut().limit = size;
198 for i in 1u32.. {
199 let mut key_data = i.to_le_bytes().to_vec();
200 while key_data.last() == Some(&0) {
201 key_data.pop();
202 }
203 let key = Key::try_from_var(key_data).unwrap();
204 if let Err(e) = ext.set_transient_storage(&key, Some(Vec::new()), false) {
205 ext.transient_storage().meter().current_mut().limit = limit;
207 ext.transient_storage().meter().current_mut().amount = amount;
208 if e == Error::<T>::OutOfTransientStorage.into() {
209 break;
210 } else {
211 return Err("Initialization of the transient storage failed");
212 }
213 }
214 }
215 Ok(())
216 }
217}
218
219pub fn default_deposit_limit<T: Config>() -> BalanceOf<T> {
221 <BalanceOf<T>>::max_value()
222}
223
224pub fn caller_funding<T: Config>() -> BalanceOf<T> {
226 BalanceOf::<T>::max_value() / 10_000u32.into()
229}
230
231#[derive(Clone)]
233pub struct Contract<T: Config> {
234 pub caller: T::AccountId,
235 pub account_id: T::AccountId,
236 pub address: H160,
237}
238
239impl<T> Contract<T>
240where
241 T: Config,
242{
243 pub fn new(module: VmBinaryModule, data: Vec<u8>) -> Result<Contract<T>, &'static str> {
245 let caller = T::AddressMapper::to_fallback_account_id(&crate::test_utils::ALICE_ADDR);
246 Self::with_caller(caller, module, data)
247 }
248
249 #[cfg(feature = "runtime-benchmarks")]
251 pub fn with_index(
252 index: u32,
253 module: VmBinaryModule,
254 data: Vec<u8>,
255 ) -> Result<Contract<T>, &'static str> {
256 Self::with_caller(frame_benchmarking::account("instantiator", index, 0), module, data)
257 }
258
259 pub fn with_caller(
261 caller: T::AccountId,
262 module: VmBinaryModule,
263 data: Vec<u8>,
264 ) -> Result<Contract<T>, &'static str> {
265 T::Currency::set_balance(&caller, caller_funding::<T>());
266 let salt = Some([0xffu8; 32]);
267 let origin: OriginFor<T> = RawOrigin::Signed(caller.clone()).into();
268
269 Contracts::<T>::map_account(origin.clone()).ok();
271
272 #[cfg(feature = "runtime-benchmarks")]
273 frame_benchmarking::benchmarking::add_to_whitelist(
274 frame_system::Account::<T>::hashed_key_for(&caller).into(),
275 );
276
277 let outcome = Contracts::<T>::bare_instantiate(
278 origin,
279 U256::zero(),
280 TransactionLimits::WeightAndDeposit {
281 weight_limit: Weight::MAX,
282 deposit_limit: default_deposit_limit::<T>(),
283 },
284 Code::Upload(module.code),
285 data,
286 salt,
287 &ExecConfig::new_substrate_tx(),
288 );
289
290 let address = outcome.result?.addr;
291 let account_id = T::AddressMapper::to_fallback_account_id(&address);
292 let result = Contract { caller, address, account_id };
293
294 AccountInfo::<T>::insert_contract(&address, result.info()?);
295 Ok(result)
296 }
297
298 pub fn with_storage(
300 code: VmBinaryModule,
301 stor_num: u32,
302 stor_size: u32,
303 ) -> Result<Self, &'static str> {
304 let contract = Contract::<T>::new(code, vec![])?;
305 let storage_items = (0..stor_num)
306 .map(|i| {
307 let hash = T::Hashing::hash_of(&i)
308 .as_ref()
309 .try_into()
310 .map_err(|_| "Hash too big for storage key")?;
311 Ok((hash, vec![42u8; stor_size as usize]))
312 })
313 .collect::<Result<Vec<_>, &'static str>>()?;
314 contract.store(&storage_items)?;
315 Ok(contract)
316 }
317
318 pub fn store(&self, items: &Vec<([u8; 32], Vec<u8>)>) -> Result<(), &'static str> {
320 let info = self.info()?;
321 for item in items {
322 info.write(&Key::Fix(item.0), Some(item.1.clone()), None, false)
323 .map_err(|_| "Failed to write storage to restoration dest")?;
324 }
325
326 AccountInfo::<T>::insert_contract(&self.address, info);
327 Ok(())
328 }
329
330 pub fn with_unbalanced_storage_trie(
332 code: VmBinaryModule,
333 key: &[u8],
334 ) -> Result<Self, &'static str> {
335 const UNBALANCED_TRIE_LAYERS: u32 = 20;
337
338 if (key.len() as u32) < UNBALANCED_TRIE_LAYERS.div_ceil(2) {
339 return Err("Key size too small to create the specified trie");
340 }
341
342 let value = vec![16u8; limits::STORAGE_BYTES as usize];
343 let contract = Contract::<T>::new(code, vec![])?;
344 let info = contract.info()?;
345 let child_trie_info = info.child_trie_info();
346 child::put_raw(&child_trie_info, &key, &value);
347 for l in 0..UNBALANCED_TRIE_LAYERS {
348 let pos = l as usize / 2;
349 let mut key_new = key.to_vec();
350 for i in 0u8..16 {
351 key_new[pos] = if l % 2 == 0 {
352 (key_new[pos] & 0xF0) | i
353 } else {
354 (key_new[pos] & 0x0F) | (i << 4)
355 };
356
357 if key == &key_new {
358 continue;
359 }
360 child::put_raw(&child_trie_info, &key_new, &value);
361 }
362 }
363 Ok(contract)
364 }
365
366 pub fn address_info(addr: &T::AccountId) -> Result<ContractInfo<T>, &'static str> {
368 <AccountInfo<T>>::load_contract(&T::AddressMapper::to_address(addr))
369 .ok_or("Expected contract to exist at this point.")
370 }
371
372 pub fn info(&self) -> Result<ContractInfo<T>, &'static str> {
374 Self::address_info(&self.account_id)
375 }
376
377 pub fn set_balance(&self, value: impl Into<BalanceWithDust<BalanceOf<T>>>) {
379 let (value, dust) = value.into().deconstruct();
380 T::Currency::set_balance(&self.account_id, value);
381 crate::AccountInfoOf::<T>::mutate(&self.address, |account| {
382 account.as_mut().map(|a| a.dust = dust);
383 });
384 }
385
386 pub fn code_exists(hash: &sp_core::H256) -> bool {
388 <PristineCode<T>>::contains_key(hash) && <CodeInfoOf<T>>::contains_key(&hash)
389 }
390
391 pub fn code_removed(hash: &sp_core::H256) -> bool {
393 !<PristineCode<T>>::contains_key(hash) && !<CodeInfoOf<T>>::contains_key(&hash)
394 }
395}
396
397#[derive(Clone)]
399pub struct VmBinaryModule {
400 pub code: Vec<u8>,
401 pub hash: H256,
402}
403
404impl VmBinaryModule {
405 pub fn dummy() -> Self {
407 Self::new(bench_fixtures::dummy().to_vec())
408 }
409
410 fn new(code: Vec<u8>) -> Self {
411 let hash = keccak_256(&code);
412 Self { code, hash: H256(hash) }
413 }
414}
415
416#[cfg(any(test, feature = "runtime-benchmarks"))]
417impl VmBinaryModule {
418 pub fn evm_init_code_for_runtime_size(size: u32) -> Self {
422 use revm::bytecode::opcode::{PUSH1, PUSH3, RETURN};
423 assert!(size <= 0x00FF_FFFF, "size {size} exceeds PUSH3 max (16MiB - 1)");
424 let [_, b1, b2, b3] = size.to_be_bytes();
425 let code = vec![
426 PUSH3, b1, b2, b3, PUSH1, 0, RETURN, ];
430 Self::new(code)
431 }
432}
433
434#[cfg(feature = "runtime-benchmarks")]
435impl VmBinaryModule {
436 pub fn dummy_unique(replace_with: u32) -> Self {
438 Self::new(bench_fixtures::dummy_unique(replace_with))
439 }
440
441 pub fn sized(size: u32) -> Self {
446 Self::with_num_instructions(size / 3)
449 }
450
451 pub fn with_num_instructions(num_instructions: u32) -> Self {
462 use alloc::{fmt::Write, string::ToString};
463 let mut text = "
464 pub @deploy:
465 ret
466 pub @call:
467 "
468 .to_string();
469 for i in 0..num_instructions {
470 match i {
471 0 => writeln!(text, "ecalli {}", crate::SENTINEL).unwrap(),
474 i if i % (limits::code::BASIC_BLOCK_SIZE - 1) == 0 => {
475 text.push_str("fallthrough\n")
476 },
477 _ => text.push_str("a0 = a1 + a2\n"),
478 }
479 }
480 text.push_str("ret\n");
481 let code = polkavm_common::assembler::assemble(
482 Some(polkavm_common::program::InstructionSetKind::ReviveV1),
483 &text,
484 )
485 .unwrap();
486 Self::new(code)
487 }
488
489 pub fn noop() -> Self {
491 Self::new(bench_fixtures::noop().to_vec())
492 }
493
494 pub fn instr(do_load: bool) -> Self {
496 let load = match do_load {
497 false => "",
498 true => "a0 = u64 [a0]",
499 };
500 let text = alloc::format!(
501 "
502 pub @deploy:
503 ret
504 pub @call:
505 @loop:
506 jump @done if t0 == a1
507 {load}
508 t0 = t0 + 1
509 jump @loop
510 @done:
511 ret
512 "
513 );
514 let code = polkavm_common::assembler::assemble(
515 Some(polkavm_common::program::InstructionSetKind::ReviveV1),
516 &text,
517 )
518 .unwrap();
519 Self::new(code)
520 }
521
522 pub fn evm_noop(size: u32) -> Self {
524 use revm::bytecode::opcode::JUMPDEST;
525
526 let code = vec![JUMPDEST; size as usize];
527 Self::new(code)
528 }
529}