referrerpolicy=no-referrer-when-downgrade

pallet_revive/vm/pvm/
env.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 super::*;
19
20use crate::{
21	AccountIdOf, CodeInfo, Config, ContractBlob, Error, SENTINEL, Weight,
22	address::AddressMapper,
23	debug::DebugSettings,
24	exec::Ext,
25	limits,
26	primitives::ExecReturnValue,
27	vm::{BytecodeType, ExportedFunction, RuntimeCosts, calculate_code_deposit},
28};
29use alloc::vec::Vec;
30use core::mem;
31use frame_support::traits::Get;
32use pallet_revive_proc_macro::define_env;
33use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags};
34use sp_core::U256;
35use sp_io::hashing::keccak_256;
36use sp_runtime::{DispatchError, SaturatedConversion};
37
38impl<T: Config> ContractBlob<T> {
39	/// Compile and instantiate contract.
40	///
41	/// `aux_data_size` is only used for runtime benchmarks. Real contracts
42	/// don't make use of this buffer. Hence this should not be set to anything
43	/// other than `0` when not used for benchmarking.
44	pub fn prepare_call<E: Ext<T = T>>(
45		self,
46		mut runtime: Runtime<E, polkavm::RawInstance>,
47		entry_point: ExportedFunction,
48		aux_data_size: u32,
49	) -> Result<PreparedCall<E>, ExecError> {
50		let mut config = polkavm::Config::default();
51		// Log filtering by level with log::enabled! returns always true,
52		// passing all logs through impacting performance \
53		// (more details: https://github.com/paritytech/polkadot-sdk/issues/8760#issuecomment-3499548774)
54		// By default, disable polkavm logging unless pvm_logs debug setting is enabled.
55		let pvm_logs_enabled = DebugSettings::is_pvm_logs_enabled::<T>();
56		config.set_imperfect_logger_filtering_workaround(!pvm_logs_enabled);
57		config.set_backend(Some(polkavm::BackendKind::Interpreter));
58		config.set_cache_enabled(false);
59		#[cfg(feature = "std")]
60		if std::env::var_os("REVIVE_USE_COMPILER").is_some() {
61			log::warn!(target: LOG_TARGET, "Using PolkaVM compiler backend because env var REVIVE_USE_COMPILER is set");
62			config.set_backend(Some(polkavm::BackendKind::Compiler));
63		}
64		let engine = polkavm::Engine::new(&config).expect(
65			"on-chain (no_std) use of interpreter is hard coded.
66				interpreter is available on all platforms; qed",
67		);
68
69		let mut module_config = polkavm::ModuleConfig::new();
70		module_config.set_page_size(limits::PAGE_SIZE);
71		module_config.set_gas_metering(Some(polkavm::GasMeteringKind::Sync));
72		module_config.set_aux_data_size(aux_data_size);
73		let module =
74			polkavm::Module::new(&engine, &module_config, self.code.into()).map_err(|err| {
75				log::debug!(target: LOG_TARGET, "failed to create polkavm module: {err:?}");
76				Error::<T>::CodeRejected
77			})?;
78
79		let entry_program_counter = module
80			.exports()
81			.find(|export| export.symbol().as_bytes() == entry_point.identifier().as_bytes())
82			.ok_or_else(|| <Error<T>>::CodeRejected)?
83			.program_counter();
84
85		let gas_limit_polkavm: polkavm::Gas = runtime.ext().frame_meter_mut().sync_to_executor();
86
87		let mut instance = module.instantiate().map_err(|err| {
88			log::debug!(target: LOG_TARGET, "failed to instantiate polkavm module: {err:?}");
89			Error::<T>::CodeRejected
90		})?;
91
92		instance.set_gas(gas_limit_polkavm);
93		instance
94			.set_interpreter_cache_size_limit(Some(polkavm::SetCacheSizeLimitArgs {
95				max_block_size: limits::code::BASIC_BLOCK_SIZE,
96				max_cache_size_bytes: limits::code::INTERPRETER_CACHE_BYTES
97					.try_into()
98					.map_err(|_| Error::<T>::CodeRejected)?,
99			}))
100			.map_err(|_| Error::<T>::CodeRejected)?;
101		instance.prepare_call_untyped(entry_program_counter, &[]);
102
103		Ok(PreparedCall { module, instance, runtime })
104	}
105}
106
107impl<T: Config> ContractBlob<T> {
108	/// We only check for size and nothing else when the code is uploaded.
109	pub fn from_pvm_code(code: Vec<u8>, owner: AccountIdOf<T>) -> Result<Self, DispatchError> {
110		// We do validation only when new code is deployed. This allows us to increase
111		// the limits later without affecting already deployed code.
112		let code = limits::code::enforce::<T>(code)?;
113
114		let code_len = code.len() as u32;
115		let deposit = calculate_code_deposit::<T>(code_len);
116
117		let code_info = CodeInfo {
118			owner,
119			deposit,
120			refcount: 0,
121			code_len,
122			code_type: BytecodeType::Pvm,
123			behaviour_version: Default::default(),
124		};
125		let code_hash = H256(sp_io::hashing::keccak_256(&code));
126		Ok(ContractBlob { code, code_info, code_hash })
127	}
128}
129
130impl<'a, E: Ext, M: PolkaVmInstance<E::T>> Runtime<'a, E, M> {
131	pub fn handle_interrupt(
132		&mut self,
133		interrupt: Result<polkavm::InterruptKind, polkavm::Error>,
134		module: &polkavm::Module,
135		instance: &mut M,
136	) -> Option<ExecResult> {
137		use polkavm::InterruptKind::*;
138
139		match interrupt {
140			Err(error) => {
141				// in contrast to the other returns this "should" not happen: log level error
142				log::error!(target: LOG_TARGET, "polkavm execution error: {error}");
143				Some(Err(Error::<E::T>::ExecutionFailed.into()))
144			},
145			Ok(Finished) => {
146				Some(Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }))
147			},
148			Ok(Trap) => Some(Err(Error::<E::T>::ContractTrapped.into())),
149			Ok(Segfault(_)) => Some(Err(Error::<E::T>::ExecutionFailed.into())),
150			Ok(NotEnoughGas) => Some(Err(Error::<E::T>::OutOfGas.into())),
151			Ok(Step) => None,
152			Ok(Ecalli(idx)) => {
153				// This is a special hard coded syscall index which is used by benchmarks
154				// to abort contract execution. It is used to terminate the execution without
155				// breaking up a basic block. The fixed index is used so that the benchmarks
156				// don't have to deal with import tables.
157				if cfg!(feature = "runtime-benchmarks") && idx == SENTINEL {
158					return Some(Ok(ExecReturnValue {
159						flags: ReturnFlags::empty(),
160						data: Vec::new(),
161					}));
162				}
163				let Some(syscall_symbol) = module.imports().get(idx) else {
164					return Some(Err(<Error<E::T>>::InvalidSyscall.into()));
165				};
166				match self.handle_ecall(instance, syscall_symbol.as_bytes()) {
167					Ok(None) => None,
168					Ok(Some(return_value)) => {
169						instance.write_output(return_value);
170						None
171					},
172					Err(TrapReason::Return(ReturnData { flags, data })) => {
173						match ReturnFlags::from_bits(flags) {
174							None => Some(Err(Error::<E::T>::InvalidCallFlags.into())),
175							Some(flags) => Some(Ok(ExecReturnValue { flags, data })),
176						}
177					},
178					Err(TrapReason::Termination) => Some(Ok(Default::default())),
179					Err(TrapReason::SupervisorError(error)) => Some(Err(error.into())),
180				}
181			},
182		}
183	}
184}
185
186// This is the API exposed to contracts.
187//
188// # Note
189//
190// Any input that leads to a out of bound error (reading or writing) or failing to decode
191// data passed to the supervisor will lead to a trap. This is not documented explicitly
192// for every function.
193#[define_env]
194pub mod env {
195	/// Noop function used to benchmark the time it takes to execute an empty function.
196	#[cfg(feature = "runtime-benchmarks")]
197	fn noop(&mut self, memory: &mut M) -> Result<(), TrapReason> {
198		Ok(())
199	}
200
201	/// Set the value at the given key in the contract storage.
202	/// See [`pallet_revive_uapi::HostFn::set_storage`]
203	#[mutating]
204	fn set_storage(
205		&mut self,
206		memory: &mut M,
207		flags: u32,
208		key_ptr: u32,
209		key_len: u32,
210		value_ptr: u32,
211		value_len: u32,
212	) -> Result<u32, TrapReason> {
213		self.set_storage(
214			memory,
215			flags,
216			key_ptr,
217			key_len,
218			StorageValue::Memory { ptr: value_ptr, len: value_len },
219		)
220	}
221
222	/// Sets the storage at a fixed 256-bit key with a fixed 256-bit value.
223	/// See [`pallet_revive_uapi::HostFn::set_storage_or_clear`].
224	#[mutating]
225	fn set_storage_or_clear(
226		&mut self,
227		memory: &mut M,
228		flags: u32,
229		key_ptr: u32,
230		value_ptr: u32,
231	) -> Result<u32, TrapReason> {
232		let value = memory.read(value_ptr, 32)?;
233
234		if value.iter().all(|&b| b == 0) {
235			self.clear_storage(memory, flags, key_ptr, SENTINEL)
236		} else {
237			self.set_storage(memory, flags, key_ptr, SENTINEL, StorageValue::Value(value))
238		}
239	}
240
241	/// Retrieve the value under the given key from storage.
242	/// See [`pallet_revive_uapi::HostFn::get_storage`]
243	fn get_storage(
244		&mut self,
245		memory: &mut M,
246		flags: u32,
247		key_ptr: u32,
248		key_len: u32,
249		out_ptr: u32,
250		out_len_ptr: u32,
251	) -> Result<ReturnErrorCode, TrapReason> {
252		self.get_storage(
253			memory,
254			flags,
255			key_ptr,
256			key_len,
257			out_ptr,
258			StorageReadMode::VariableOutput { output_len_ptr: out_len_ptr },
259		)
260	}
261
262	/// Reads the storage at a fixed 256-bit key and writes back a fixed 256-bit value.
263	/// See [`pallet_revive_uapi::HostFn::get_storage_or_zero`].
264	fn get_storage_or_zero(
265		&mut self,
266		memory: &mut M,
267		flags: u32,
268		key_ptr: u32,
269		out_ptr: u32,
270	) -> Result<(), TrapReason> {
271		let _ = self.get_storage(
272			memory,
273			flags,
274			key_ptr,
275			SENTINEL,
276			out_ptr,
277			StorageReadMode::FixedOutput32,
278		)?;
279
280		Ok(())
281	}
282
283	/// Make a call to another contract.
284	/// See [`pallet_revive_uapi::HostFn::call`].
285	fn call(
286		&mut self,
287		memory: &mut M,
288		flags_and_callee: u64,
289		ref_time_limit: u64,
290		proof_size_limit: u64,
291		deposit_and_value: u64,
292		input_data: u64,
293		output_data: u64,
294	) -> Result<ReturnErrorCode, TrapReason> {
295		let (flags, callee_ptr) = extract_hi_lo(flags_and_callee);
296		let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value);
297		let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
298		let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
299		let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
300
301		self.charge_gas(RuntimeCosts::CopyFromContract(32))?;
302		let deposit_limit = memory.read_u256(deposit_ptr)?;
303
304		self.call(
305			memory,
306			CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
307			CallType::Call { value_ptr },
308			callee_ptr,
309			&CallResources::from_weight_and_deposit(weight, deposit_limit),
310			input_data_ptr,
311			input_data_len,
312			output_ptr,
313			output_len_ptr,
314		)
315	}
316
317	/// Make a call to another contract.
318	/// See [`pallet_revive_uapi::HostFn::call_evm`].
319	fn call_evm(
320		&mut self,
321		memory: &mut M,
322		flags: u32,
323		callee: u32,
324		value_ptr: u32,
325		gas: u64,
326		input_data: u64,
327		output_data: u64,
328	) -> Result<ReturnErrorCode, TrapReason> {
329		let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
330		let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
331		let resources = if gas == u64::MAX {
332			CallResources::NoLimits
333		} else {
334			self.charge_gas(RuntimeCosts::CopyFromContract(32))?;
335			let value = memory.read_u256(value_ptr)?;
336			// We also need to detect the 2300: We need to add something scaled.
337			let add_stipend = !value.is_zero() || gas == revm::interpreter::gas::CALL_STIPEND;
338			CallResources::from_ethereum_gas(gas.into(), add_stipend)
339		};
340
341		self.call(
342			memory,
343			CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
344			CallType::Call { value_ptr },
345			callee,
346			&resources,
347			input_data_ptr,
348			input_data_len,
349			output_ptr,
350			output_len_ptr,
351		)
352	}
353
354	/// Execute code in the context (storage, caller, value) of the current contract.
355	/// See [`pallet_revive_uapi::HostFn::delegate_call`].
356	fn delegate_call(
357		&mut self,
358		memory: &mut M,
359		flags_and_callee: u64,
360		ref_time_limit: u64,
361		proof_size_limit: u64,
362		deposit_ptr: u32,
363		input_data: u64,
364		output_data: u64,
365	) -> Result<ReturnErrorCode, TrapReason> {
366		let (flags, address_ptr) = extract_hi_lo(flags_and_callee);
367		let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
368		let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
369		let weight = Weight::from_parts(ref_time_limit, proof_size_limit);
370
371		self.charge_gas(RuntimeCosts::CopyFromContract(32))?;
372		let deposit_limit = memory.read_u256(deposit_ptr)?;
373
374		self.call(
375			memory,
376			CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
377			CallType::DelegateCall,
378			address_ptr,
379			&CallResources::from_weight_and_deposit(weight, deposit_limit),
380			input_data_ptr,
381			input_data_len,
382			output_ptr,
383			output_len_ptr,
384		)
385	}
386
387	/// Same as `delegate_call` but with EVM gas.
388	/// See [`pallet_revive_uapi::HostFn::delegate_call_evm`].
389	fn delegate_call_evm(
390		&mut self,
391		memory: &mut M,
392		flags: u32,
393		callee: u32,
394		gas: u64,
395		input_data: u64,
396		output_data: u64,
397	) -> Result<ReturnErrorCode, TrapReason> {
398		let (input_data_len, input_data_ptr) = extract_hi_lo(input_data);
399		let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
400		let resources = if gas == u64::MAX {
401			CallResources::NoLimits
402		} else {
403			CallResources::from_ethereum_gas(gas.into(), false)
404		};
405
406		self.call(
407			memory,
408			CallFlags::from_bits(flags).ok_or(Error::<E::T>::InvalidCallFlags)?,
409			CallType::DelegateCall,
410			callee,
411			&resources,
412			input_data_ptr,
413			input_data_len,
414			output_ptr,
415			output_len_ptr,
416		)
417	}
418
419	/// Instantiate a contract with the specified code hash.
420	/// See [`pallet_revive_uapi::HostFn::instantiate`].
421	#[mutating]
422	fn instantiate(
423		&mut self,
424		memory: &mut M,
425		ref_time_limit: u64,
426		proof_size_limit: u64,
427		deposit_and_value: u64,
428		input_data: u64,
429		output_data: u64,
430		address_and_salt: u64,
431	) -> Result<ReturnErrorCode, TrapReason> {
432		let (deposit_ptr, value_ptr) = extract_hi_lo(deposit_and_value);
433		let (input_data_len, code_hash_ptr) = extract_hi_lo(input_data);
434		let (output_len_ptr, output_ptr) = extract_hi_lo(output_data);
435		let (address_ptr, salt_ptr) = extract_hi_lo(address_and_salt);
436		let Some(input_data_ptr) = code_hash_ptr.checked_add(32) else {
437			return Err(Error::<E::T>::OutOfBounds.into());
438		};
439		let Some(input_data_len) = input_data_len.checked_sub(32) else {
440			return Err(Error::<E::T>::OutOfBounds.into());
441		};
442
443		self.instantiate(
444			memory,
445			code_hash_ptr,
446			Weight::from_parts(ref_time_limit, proof_size_limit),
447			deposit_ptr,
448			value_ptr,
449			input_data_ptr,
450			input_data_len,
451			address_ptr,
452			output_ptr,
453			output_len_ptr,
454			salt_ptr,
455		)
456	}
457
458	/// Returns the total size of the contract call input data.
459	/// See [`pallet_revive_uapi::HostFn::call_data_size `].
460	fn call_data_size(&mut self, memory: &mut M) -> Result<u64, TrapReason> {
461		self.charge_gas(RuntimeCosts::CallDataSize)?;
462		Ok(self
463			.input_data
464			.as_ref()
465			.map(|input| input.len().try_into().expect("usize fits into u64; qed"))
466			.unwrap_or_default())
467	}
468
469	/// Stores the input passed by the caller into the supplied buffer.
470	/// See [`pallet_revive_uapi::HostFn::call_data_copy`].
471	fn call_data_copy(
472		&mut self,
473		memory: &mut M,
474		out_ptr: u32,
475		out_len: u32,
476		offset: u32,
477	) -> Result<(), TrapReason> {
478		self.charge_gas(RuntimeCosts::CallDataCopy(out_len))?;
479
480		let Some(input) = self.input_data.as_ref() else {
481			return Err(Error::<E::T>::InputForwarded.into());
482		};
483
484		let start = offset as usize;
485		if start >= input.len() {
486			memory.zero(out_ptr, out_len)?;
487			return Ok(());
488		}
489
490		let end = start.saturating_add(out_len as usize).min(input.len());
491		memory.write(out_ptr, &input[start..end])?;
492
493		let bytes_written = (end - start) as u32;
494		memory.zero(out_ptr.saturating_add(bytes_written), out_len - bytes_written)?;
495
496		Ok(())
497	}
498
499	/// Stores the U256 value at given call input `offset` into the supplied buffer.
500	/// See [`pallet_revive_uapi::HostFn::call_data_load`].
501	fn call_data_load(
502		&mut self,
503		memory: &mut M,
504		out_ptr: u32,
505		offset: u32,
506	) -> Result<(), TrapReason> {
507		self.charge_gas(RuntimeCosts::CallDataLoad)?;
508
509		let Some(input) = self.input_data.as_ref() else {
510			return Err(Error::<E::T>::InputForwarded.into());
511		};
512
513		let mut data = [0; 32];
514		let start = offset as usize;
515		let data = if start >= input.len() {
516			data // Any index is valid to request; OOB offsets return zero.
517		} else {
518			let end = start.saturating_add(32).min(input.len());
519			data[..end - start].copy_from_slice(&input[start..end]);
520			data.reverse();
521			data // Solidity expects right-padded data
522		};
523
524		self.write_fixed_sandbox_output(memory, out_ptr, &data, false, already_charged)?;
525
526		Ok(())
527	}
528
529	/// Cease contract execution and save a data buffer as a result of the execution.
530	/// See [`pallet_revive_uapi::HostFn::return_value`].
531	fn seal_return(
532		&mut self,
533		memory: &mut M,
534		flags: u32,
535		data_ptr: u32,
536		data_len: u32,
537	) -> Result<(), TrapReason> {
538		self.charge_gas(RuntimeCosts::CopyFromContract(data_len))?;
539		if data_len > limits::CALLDATA_BYTES {
540			Err(<Error<E::T>>::ReturnDataTooLarge)?;
541		}
542		Err(TrapReason::Return(ReturnData { flags, data: memory.read(data_ptr, data_len)? }))
543	}
544
545	/// Stores the address of the caller into the supplied buffer.
546	/// See [`pallet_revive_uapi::HostFn::caller`].
547	fn caller(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
548		self.charge_gas(RuntimeCosts::Caller)?;
549		let caller = <E::T as Config>::AddressMapper::to_address(self.ext.caller().account_id()?);
550		Ok(self.write_fixed_sandbox_output(
551			memory,
552			out_ptr,
553			caller.as_bytes(),
554			false,
555			already_charged,
556		)?)
557	}
558
559	/// Stores the address of the call stack origin into the supplied buffer.
560	/// See [`pallet_revive_uapi::HostFn::origin`].
561	fn origin(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
562		self.charge_gas(RuntimeCosts::Origin)?;
563		let origin = <E::T as Config>::AddressMapper::to_address(self.ext.origin().account_id()?);
564		Ok(self.write_fixed_sandbox_output(
565			memory,
566			out_ptr,
567			origin.as_bytes(),
568			false,
569			already_charged,
570		)?)
571	}
572
573	/// Retrieve the code hash for a specified contract address.
574	/// See [`pallet_revive_uapi::HostFn::code_hash`].
575	fn code_hash(&mut self, memory: &mut M, addr_ptr: u32, out_ptr: u32) -> Result<(), TrapReason> {
576		self.charge_gas(RuntimeCosts::CodeHash)?;
577		let address = memory.read_h160(addr_ptr)?;
578		Ok(self.write_fixed_sandbox_output(
579			memory,
580			out_ptr,
581			&self.ext.code_hash(&address).as_bytes(),
582			false,
583			already_charged,
584		)?)
585	}
586
587	/// Retrieve the code size for a given contract address.
588	/// See [`pallet_revive_uapi::HostFn::code_size`].
589	fn code_size(&mut self, memory: &mut M, addr_ptr: u32) -> Result<u64, TrapReason> {
590		self.charge_gas(RuntimeCosts::CodeSize)?;
591		let address = memory.read_h160(addr_ptr)?;
592		Ok(self.ext.code_size(&address))
593	}
594
595	/// Stores the address of the current contract into the supplied buffer.
596	/// See [`pallet_revive_uapi::HostFn::address`].
597	fn address(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
598		self.charge_gas(RuntimeCosts::Address)?;
599		let address = self.ext.address();
600		Ok(self.write_fixed_sandbox_output(
601			memory,
602			out_ptr,
603			address.as_bytes(),
604			false,
605			already_charged,
606		)?)
607	}
608
609	/// Stores the immutable data into the supplied buffer.
610	/// See [`pallet_revive_uapi::HostFn::get_immutable_data`].
611	fn get_immutable_data(
612		&mut self,
613		memory: &mut M,
614		out_ptr: u32,
615		out_len_ptr: u32,
616	) -> Result<(), TrapReason> {
617		// `immutable_data_len` is exact for a frame executing its own code and the
618		// `IMMUTABLE_BYTES` cap when the code (and so the data) belongs to another account —
619		// delegate calls, and calls to EIP-7702 delegated EOAs. Charge it, then settle on the
620		// actual length.
621		let charge_len = self.ext.immutable_data_len();
622		let charged = self.charge_gas(RuntimeCosts::GetImmutableData(charge_len))?;
623		let data = self.ext.get_immutable_data()?;
624		self.adjust_gas(charged, RuntimeCosts::GetImmutableData(data.len() as u32));
625		self.write_sandbox_output(memory, out_ptr, out_len_ptr, &data, false, already_charged)?;
626		Ok(())
627	}
628
629	/// Attaches the supplied immutable data to the currently executing contract.
630	/// See [`pallet_revive_uapi::HostFn::set_immutable_data`].
631	fn set_immutable_data(&mut self, memory: &mut M, ptr: u32, len: u32) -> Result<(), TrapReason> {
632		if len > limits::IMMUTABLE_BYTES {
633			return Err(Error::<E::T>::OutOfBounds.into());
634		}
635		self.charge_gas(RuntimeCosts::SetImmutableData(len))?;
636		let buf = memory.read(ptr, len)?;
637		let data = buf.try_into().expect("bailed out earlier; qed");
638		self.ext.set_immutable_data(data)?;
639		Ok(())
640	}
641
642	/// Stores the *free* balance of the current account into the supplied buffer.
643	/// See [`pallet_revive_uapi::HostFn::balance`].
644	fn balance(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
645		self.charge_gas(RuntimeCosts::Balance)?;
646		Ok(self.write_fixed_sandbox_output(
647			memory,
648			out_ptr,
649			&self.ext.balance().to_little_endian(),
650			false,
651			already_charged,
652		)?)
653	}
654
655	/// Stores the *free* balance of the supplied address into the supplied buffer.
656	/// See [`pallet_revive_uapi::HostFn::balance`].
657	fn balance_of(
658		&mut self,
659		memory: &mut M,
660		addr_ptr: u32,
661		out_ptr: u32,
662	) -> Result<(), TrapReason> {
663		self.charge_gas(RuntimeCosts::BalanceOf)?;
664		let address = memory.read_h160(addr_ptr)?;
665		Ok(self.write_fixed_sandbox_output(
666			memory,
667			out_ptr,
668			&self.ext.balance_of(&address).to_little_endian(),
669			false,
670			already_charged,
671		)?)
672	}
673
674	/// Returns the chain ID.
675	/// See [`pallet_revive_uapi::HostFn::chain_id`].
676	fn chain_id(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
677		Ok(self.write_fixed_sandbox_output(
678			memory,
679			out_ptr,
680			&U256::from(<E::T as Config>::ChainId::get()).to_little_endian(),
681			false,
682			|_| Some(RuntimeCosts::CopyToContract(32)),
683		)?)
684	}
685
686	/// Returns the block ref_time limit.
687	/// See [`pallet_revive_uapi::HostFn::gas_limit`].
688	fn gas_limit(&mut self, memory: &mut M) -> Result<u64, TrapReason> {
689		self.charge_gas(RuntimeCosts::GasLimit)?;
690		Ok(self.ext.gas_limit())
691	}
692
693	/// Stores the value transferred along with this call/instantiate into the supplied buffer.
694	/// See [`pallet_revive_uapi::HostFn::value_transferred`].
695	fn value_transferred(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
696		self.charge_gas(RuntimeCosts::ValueTransferred)?;
697		Ok(self.write_fixed_sandbox_output(
698			memory,
699			out_ptr,
700			&self.ext.value_transferred().to_little_endian(),
701			false,
702			already_charged,
703		)?)
704	}
705
706	/// Returns the simulated ethereum `GASPRICE` value.
707	/// See [`pallet_revive_uapi::HostFn::gas_price`].
708	fn gas_price(&mut self, memory: &mut M) -> Result<u64, TrapReason> {
709		self.charge_gas(RuntimeCosts::GasPrice)?;
710		Ok(self.ext.effective_gas_price().saturated_into())
711	}
712
713	/// Returns the simulated ethereum `BASEFEE` value.
714	/// See [`pallet_revive_uapi::HostFn::base_fee`].
715	fn base_fee(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
716		self.charge_gas(RuntimeCosts::BaseFee)?;
717		Ok(self.write_fixed_sandbox_output(
718			memory,
719			out_ptr,
720			&Pallet::<E::T>::evm_base_fee().to_little_endian(),
721			false,
722			already_charged,
723		)?)
724	}
725
726	/// Load the latest block timestamp into the supplied buffer
727	/// See [`pallet_revive_uapi::HostFn::now`].
728	fn now(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
729		self.charge_gas(RuntimeCosts::Now)?;
730		Ok(self.write_fixed_sandbox_output(
731			memory,
732			out_ptr,
733			&self.ext.now().to_little_endian(),
734			false,
735			already_charged,
736		)?)
737	}
738
739	/// Deposit a contract event with the data buffer and optional list of topics.
740	/// See [pallet_revive_uapi::HostFn::deposit_event]
741	#[mutating]
742	fn deposit_event(
743		&mut self,
744		memory: &mut M,
745		topics_ptr: u32,
746		num_topic: u32,
747		data_ptr: u32,
748		data_len: u32,
749	) -> Result<(), TrapReason> {
750		self.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len })?;
751
752		if num_topic > limits::NUM_EVENT_TOPICS {
753			return Err(Error::<E::T>::TooManyTopics.into());
754		}
755
756		if data_len > limits::EVENT_BYTES {
757			return Err(Error::<E::T>::ValueTooLarge.into());
758		}
759
760		let topics: Vec<H256> = match num_topic {
761			0 => Vec::new(),
762			_ => {
763				let mut v = Vec::with_capacity(num_topic as usize);
764				let topics_len = num_topic * H256::len_bytes() as u32;
765				let buf = memory.read(topics_ptr, topics_len)?;
766				for chunk in buf.chunks_exact(H256::len_bytes()) {
767					v.push(H256::from_slice(chunk));
768				}
769				v
770			},
771		};
772
773		let event_data = memory.read(data_ptr, data_len)?;
774		self.ext.deposit_event(topics, event_data);
775		Ok(())
776	}
777
778	/// Stores the current block number of the current contract into the supplied buffer.
779	/// See [`pallet_revive_uapi::HostFn::block_number`].
780	fn block_number(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
781		self.charge_gas(RuntimeCosts::BlockNumber)?;
782		Ok(self.write_fixed_sandbox_output(
783			memory,
784			out_ptr,
785			&self.ext.block_number().to_little_endian(),
786			false,
787			already_charged,
788		)?)
789	}
790
791	/// Stores the block hash at given block height into the supplied buffer.
792	/// See [`pallet_revive_uapi::HostFn::block_hash`].
793	fn block_hash(
794		&mut self,
795		memory: &mut M,
796		block_number_ptr: u32,
797		out_ptr: u32,
798	) -> Result<(), TrapReason> {
799		self.charge_gas(RuntimeCosts::BlockHash)?;
800		let block_number = memory.read_u256(block_number_ptr)?;
801		let block_hash = self.ext.block_hash(block_number).unwrap_or(H256::zero());
802		Ok(self.write_fixed_sandbox_output(
803			memory,
804			out_ptr,
805			&block_hash.as_bytes(),
806			false,
807			already_charged,
808		)?)
809	}
810
811	/// Stores the current block author into the supplied buffer.
812	/// See [`pallet_revive_uapi::HostFn::block_author`].
813	fn block_author(&mut self, memory: &mut M, out_ptr: u32) -> Result<(), TrapReason> {
814		self.charge_gas(RuntimeCosts::BlockAuthor)?;
815		let block_author = self.ext.block_author();
816
817		Ok(self.write_fixed_sandbox_output(
818			memory,
819			out_ptr,
820			&block_author.as_bytes(),
821			false,
822			already_charged,
823		)?)
824	}
825
826	/// Computes the KECCAK 256-bit hash on the given input buffer.
827	/// See [`pallet_revive_uapi::HostFn::hash_keccak_256`].
828	fn hash_keccak_256(
829		&mut self,
830		memory: &mut M,
831		input_ptr: u32,
832		input_len: u32,
833		output_ptr: u32,
834	) -> Result<(), TrapReason> {
835		self.charge_gas(RuntimeCosts::HashKeccak256(input_len))?;
836		Ok(self.compute_hash_on_intermediate_buffer(
837			memory, keccak_256, input_ptr, input_len, output_ptr,
838		)?)
839	}
840
841	/// Stores the length of the data returned by the last call into the supplied buffer.
842	/// See [`pallet_revive_uapi::HostFn::return_data_size`].
843	fn return_data_size(&mut self, memory: &mut M) -> Result<u64, TrapReason> {
844		self.charge_gas(RuntimeCosts::ReturnDataSize)?;
845		Ok(self
846			.ext
847			.last_frame_output()
848			.data
849			.len()
850			.try_into()
851			.expect("usize fits into u64; qed"))
852	}
853
854	/// Stores data returned by the last call, starting from `offset`, into the supplied buffer.
855	/// See [`pallet_revive_uapi::HostFn::return_data_copy`].
856	fn return_data_copy(
857		&mut self,
858		memory: &mut M,
859		out_ptr: u32,
860		out_len_ptr: u32,
861		offset: u32,
862	) -> Result<(), TrapReason> {
863		let output = mem::take(self.ext.last_frame_output_mut());
864		let result = if offset as usize > output.data.len() {
865			Err(Error::<E::T>::OutOfBounds.into())
866		} else {
867			self.write_sandbox_output(
868				memory,
869				out_ptr,
870				out_len_ptr,
871				&output.data[offset as usize..],
872				false,
873				|len| Some(RuntimeCosts::CopyToContract(len)),
874			)
875		};
876		*self.ext.last_frame_output_mut() = output;
877		Ok(result?)
878	}
879
880	/// Returns the amount of evm gas left.
881	///
882	/// The name is only for historical reasons as renaming functions
883	/// would be a breaking change.
884	///
885	/// See [`pallet_revive_uapi::HostFn::gas_left`].
886	fn ref_time_left(&mut self, memory: &mut M) -> Result<u64, TrapReason> {
887		self.charge_gas(RuntimeCosts::RefTimeLeft)?;
888		Ok(self.ext.gas_left())
889	}
890
891	/// Reverts the execution without data and cedes all remaining gas.
892	///
893	/// See [`pallet_revive_uapi::HostFn::consume_all_gas`].
894	fn consume_all_gas(&mut self, memory: &mut M) -> Result<(), TrapReason> {
895		self.ext.frame_meter_mut().consume_all_weight();
896		Err(TrapReason::Return(ReturnData {
897			flags: ReturnFlags::REVERT.bits(),
898			data: Default::default(),
899		}))
900	}
901
902	/// Calculates Ethereum address from the ECDSA compressed public key and stores
903	/// See `uapi/sol/ISystem.sol`.
904	fn ecdsa_to_eth_address(
905		&mut self,
906		memory: &mut M,
907		key_ptr: u32,
908		out_ptr: u32,
909	) -> Result<ReturnErrorCode, TrapReason> {
910		self.charge_gas(RuntimeCosts::EcdsaToEthAddress)?;
911		let mut compressed_key: [u8; 33] = [0; 33];
912		memory.read_into_buf(key_ptr, &mut compressed_key)?;
913		let result = self.ext.ecdsa_to_eth_address(&compressed_key);
914		match result {
915			Ok(eth_address) => {
916				memory.write(out_ptr, eth_address.as_ref())?;
917				Ok(ReturnErrorCode::Success)
918			},
919			Err(_) => Ok(ReturnErrorCode::EcdsaRecoveryFailed),
920		}
921	}
922
923	/// Verify a sr25519 signature
924	/// See `uapi/sol/ISystem.sol`.
925	fn sr25519_verify(
926		&mut self,
927		memory: &mut M,
928		signature_ptr: u32,
929		pub_key_ptr: u32,
930		message_len: u32,
931		message_ptr: u32,
932	) -> Result<ReturnErrorCode, TrapReason> {
933		self.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?;
934
935		let mut signature: [u8; 64] = [0; 64];
936		memory.read_into_buf(signature_ptr, &mut signature)?;
937
938		let mut pub_key: [u8; 32] = [0; 32];
939		memory.read_into_buf(pub_key_ptr, &mut pub_key)?;
940
941		let message: Vec<u8> = memory.read(message_ptr, message_len)?;
942
943		if self.ext.sr25519_verify(&signature, &message, &pub_key) {
944			Ok(ReturnErrorCode::Success)
945		} else {
946			Ok(ReturnErrorCode::Sr25519VerifyFailed)
947		}
948	}
949
950	/// Remove the calling account and transfer remaining balance:
951	/// **total** balance if code is deleted from storage, else **free** balance only.
952	/// See [`pallet_revive_uapi::HostFn::terminate`].
953	#[mutating]
954	fn terminate(&mut self, memory: &mut M, beneficiary_ptr: u32) -> Result<(), TrapReason> {
955		let charged = self.charge_gas(RuntimeCosts::Terminate { code_removed: true })?;
956		let beneficiary = memory.read_h160(beneficiary_ptr)?;
957		if matches!(self.ext.terminate_if_same_tx(&beneficiary)?, crate::CodeRemoved::No) {
958			self.adjust_gas(charged, RuntimeCosts::Terminate { code_removed: false });
959		}
960		Err(TrapReason::Termination)
961	}
962}
963
964#[cfg(test)]
965mod tests {
966	use super::*;
967	use pallet_revive_types::runtime_api::*;
968	use strum::VariantArray;
969
970	#[test]
971	fn wire_has_all_syscalls() {
972		let wire_syscalls = PolkavmSyscallV1::VARIANTS
973			.iter()
974			.copied()
975			.map(<&'static str>::from)
976			.collect::<Vec<_>>();
977		let canonical_syscalls = list_trace_ops()
978			.iter()
979			.map(|bytes| str::from_utf8(bytes).unwrap())
980			.collect::<Vec<_>>();
981
982		assert_eq!(
983			wire_syscalls, canonical_syscalls,
984			"There is a difference between the syscalls defined for the wire and the syscalls \
985			available in the system. Wire = {wire_syscalls:?}. Available = {canonical_syscalls:?}"
986		)
987	}
988}