referrerpolicy=no-referrer-when-downgrade

pallet_revive/
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
18//! Types to build an environment that can be used to test and benchmark host function /
19//! pre-compiles.
20
21#![cfg(any(feature = "runtime-benchmarks", test))]
22// A lot of this code is only used in benchmarking but we also want to use the code for testing
23// pre-compiles eventually. For that we will probably export some of those types from the crate.
24// Until then we simply ignore the warnings that arise when compiling tests without runtime
25// benchmarks.
26#![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
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	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	/// Setup a new call for the given module.
76	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			// Whitelist contract account, as it is already accounted for in the call benchmark
84			frame_benchmarking::benchmarking::add_to_whitelist(
85				frame_system::Account::<T>::hashed_key_for(&contract.account_id).into(),
86			);
87
88			// Whitelist the contract's contractInfo as it is already accounted for in the call
89			// benchmark
90			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	/// Set the meter's storage deposit limit.
117	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	/// Set the call's origin.
126	pub fn set_origin(&mut self, origin: Origin<T>) {
127		self.origin = origin;
128	}
129
130	/// Set the contract's balance.
131	pub fn set_balance(&mut self, value: impl Into<BalanceWithDust<BalanceOf<T>>>) {
132		self.contract.set_balance(value);
133	}
134
135	/// Set the call's input data.
136	pub fn set_data(&mut self, value: Vec<u8>) {
137		self.data = value;
138	}
139
140	/// Set the transient storage size.
141	pub fn set_transient_storage_size(&mut self, size: u32) {
142		self.transient_storage_size = size;
143	}
144
145	/// Set the read-only flag on the call stack frame.
146	pub fn set_read_only(&mut self, read_only: bool) {
147		self.read_only = read_only;
148	}
149
150	/// Mark the call as a delegate call.
151	pub fn set_delegate_call(&mut self, delegate: bool) {
152		self.delegate_call = delegate;
153	}
154
155	/// Get the call's input data.
156	pub fn data(&self) -> Vec<u8> {
157		self.data.clone()
158	}
159
160	/// Get the call's contract.
161	pub fn contract(&self) -> Contract<T> {
162		self.contract.clone()
163	}
164
165	/// Build the call stack.
166	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	/// Prepare a call to the module.
183	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	/// Add transient_storage
195	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				// Restore previous settings.
206				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
219/// The deposit limit we use for benchmarks.
220pub fn default_deposit_limit<T: Config>() -> BalanceOf<T> {
221	<BalanceOf<T>>::max_value()
222}
223
224/// The funding that each account that either calls or instantiates contracts is funded with.
225pub fn caller_funding<T: Config>() -> BalanceOf<T> {
226	// Minting can overflow, so we can't abuse of the funding. This value happens to be big enough,
227	// but not too big to make the total supply overflow.
228	BalanceOf::<T>::max_value() / 10_000u32.into()
229}
230
231/// An instantiated and deployed contract.
232#[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	/// Create new contract and use a default account id as instantiator.
244	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	/// Create new contract and use an account id derived from the supplied index as instantiator.
250	#[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	/// Create new contract and use the supplied `caller` as instantiator.
260	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		// We ignore the error since we might also pass an already mapped account here.
270		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	/// Create a new contract with the supplied storage item count and size each.
299	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	/// Store the supplied storage items into this contracts storage.
319	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	/// Create a new contract with the specified unbalanced storage trie.
331	pub fn with_unbalanced_storage_trie(
332		code: VmBinaryModule,
333		key: &[u8],
334	) -> Result<Self, &'static str> {
335		/// Number of layers in a Radix16 unbalanced trie.
336		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	/// Get the `ContractInfo` of the `addr` or an error if it no longer exists.
367	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	/// Get the `ContractInfo` of this contract or an error if it no longer exists.
373	pub fn info(&self) -> Result<ContractInfo<T>, &'static str> {
374		Self::address_info(&self.account_id)
375	}
376
377	/// Set the balance of the contract to the supplied amount.
378	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	/// Returns `true` iff all storage entries related to code storage exist.
387	pub fn code_exists(hash: &sp_core::H256) -> bool {
388		<PristineCode<T>>::contains_key(hash) && <CodeInfoOf<T>>::contains_key(&hash)
389	}
390
391	/// Returns `true` iff no storage entry related to code storage exist.
392	pub fn code_removed(hash: &sp_core::H256) -> bool {
393		!<PristineCode<T>>::contains_key(hash) && !<CodeInfoOf<T>>::contains_key(&hash)
394	}
395}
396
397/// A vm binary module ready to be put on chain.
398#[derive(Clone)]
399pub struct VmBinaryModule {
400	pub code: Vec<u8>,
401	pub hash: H256,
402}
403
404impl VmBinaryModule {
405	/// Return a contract code that does nothing.
406	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	// Creates EVM init code that deploys `size` bytes of runtime code (all STOP opcodes).
419	// EVM memory is zero-initialized, so RETURN(0, size) produces `size` bytes of 0x00.
420	// The runtime code is what gets stored in PristineCode and loaded on every call.
421	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, // push runtime code size
427			PUSH1, 0,      // push memory offset 0
428			RETURN, // return `size` bytes from memory as runtime code
429		];
430		Self::new(code)
431	}
432}
433
434#[cfg(feature = "runtime-benchmarks")]
435impl VmBinaryModule {
436	/// Same as [`Self::dummy`] but uses `replace_with` to make the code unique.
437	pub fn dummy_unique(replace_with: u32) -> Self {
438		Self::new(bench_fixtures::dummy_unique(replace_with))
439	}
440
441	/// Same as as `with_num_instructions` but based on the blob size.
442	///
443	/// This is needed when we weigh a blob without knowing how much instructions it
444	/// contains.
445	pub fn sized(size: u32) -> Self {
446		// Due to variable length encoding of instructions this is not precise. But we only
447		// need rough numbers for our benchmarks.
448		Self::with_num_instructions(size / 3)
449	}
450
451	/// A contract code of specified number of instructions that uses all its bytes for instructions
452	/// but will return immediately.
453	///
454	/// All the basic blocks are maximum sized (only the first is important though). This is to
455	/// account for the fact that the interpreter will compile one basic block at a time even
456	/// when no code is executed. Hence this contract will trigger the compilation of a maximum
457	/// sized basic block and then return with its first instruction.
458	///
459	/// All the code will be put into the "call" export. Hence this code can be safely used for the
460	/// `instantiate_with_code` benchmark where no compilation of any block should be measured.
461	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				// return execution right away without breaking up basic block
472				// SENTINEL is a hard coded syscall that terminates execution
473				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	/// A contract code that calls the "noop" host function in a loop depending in the input.
490	pub fn noop() -> Self {
491		Self::new(bench_fixtures::noop().to_vec())
492	}
493
494	/// A contract code that does unaligned memory accessed in a loop.
495	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	/// An evm contract that executes `size` JUMPDEST instructions.
523	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}