referrerpolicy=no-referrer-when-downgrade

pallet_revive/
limits.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//! Limits that are observeable by contract code.
19//!
20//! It is important to never change this limits without supporting the old limits
21//! for already deployed contracts. This is what the [`crate::Contract::behaviour_version`]
22//! is meant for. This is true for either increasing or decreasing the limit.
23//!
24//! Limits in this file are different from the limits configured on the [`Config`] trait which are
25//! generally only affect actions that cannot be performed by a contract: For example things related
26//! to deposits and weights are allowed to be changed as they are paid by root callers which
27//! are not contracts.
28//!
29//! Exceptions to this rule apply: Limits in the [`code`] module can be increased
30//! without emulating the old values for existing contracts. Reason is that those limits are only
31//! applied **once** at code upload time. Since this action cannot be performed by contracts we
32//! can change those limits without breaking existing contracts. Please keep in mind that we should
33//! only ever **increase** those values but never decrease.
34
35/// The amount of total memory we require to safely operate.
36///
37/// This is not a config knob but derived from the limits in this file.
38pub const MEMORY_REQUIRED: u32 = memory_required();
39
40/// The maximum depth of the call stack.
41///
42/// A 0 means that no callings of other contracts are possible. In other words only the origin
43/// called "root contract" is allowed to execute then.
44pub const CALL_STACK_DEPTH: u32 = 25;
45
46/// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit.
47///
48/// We set it to the same limit that ethereum has. It is unlikely to change.
49pub const NUM_EVENT_TOPICS: u32 = 4;
50
51/// Maximum size of of the transaction payload
52///
53/// Maximum code size during instantiation taken into account plus some overhead.
54pub const MAX_TRANSACTION_PAYLOAD_SIZE: u32 = code::BLOB_BYTES + CALLDATA_BYTES;
55
56/// Maximum size of storage items.
57pub const STORAGE_BYTES: u32 = 416;
58
59/// Maximum payload size of events.
60pub const EVENT_BYTES: u32 = 64 * 1024;
61
62/// The extra ref time charge of deposit event per byte.
63///
64/// This ensure the block builder has enough memory and pallet storage
65/// to operate under worst case scenarios.
66pub const EXTRA_EVENT_CHARGE_PER_BYTE: u64 = 256 * 1024;
67
68/// The maximum size for calldata and return data.
69///
70/// Please note that the calldata is limited to 128KB on geth anyways.
71pub const CALLDATA_BYTES: u32 = 128 * 1024;
72
73/// The maximum size of the transient storage in bytes.
74///
75/// This includes keys, values, and previous entries used for storage rollback.
76pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
77
78/// The maximum allowable length in bytes for (transient) storage keys.
79pub const STORAGE_KEY_BYTES: u32 = 128;
80
81/// The page size in which PolkaVM should allocate memory chunks.
82pub const PAGE_SIZE: u32 = 4 * 1024;
83
84/// The maximum amount of immutable bytes a single contract can store.
85///
86/// The current limit of 4kb allows storing up 16 U256 immutable variables.
87/// Which should always be enough because Solidity allows for 16 local (stack) variables.
88pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
89
90/// upperbound of memory that can be used by the EVM interpreter.
91pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024;
92
93/// EVM interpreter stack limit.
94pub const EVM_STACK_LIMIT: u32 = 1024;
95
96/// Limits that are only enforced on code upload.
97///
98/// # Note
99///
100/// This limit can be increased later without breaking existing contracts
101/// as it is only enforced at code upload time. Code already uploaded
102/// will not be affected by those limits.
103pub mod code {
104	use super::PAGE_SIZE;
105	use crate::{Config, Error, LOG_TARGET};
106	use alloc::vec::Vec;
107	use sp_runtime::DispatchError;
108
109	/// The maximum length of a code blob in bytes.
110	///
111	/// This mostly exist to prevent parsing too big blobs and to
112	/// have a maximum encoded length.
113	pub const BLOB_BYTES: u32 = 1024 * 1024;
114
115	/// The maximum amount of memory the interpreter is allowed to use for compilation artifacts.
116	pub const INTERPRETER_CACHE_BYTES: u32 = 1024 * 1024;
117
118	/// The maximum size of a basic block in number of instructions.
119	///
120	/// We need to limit the size of basic blocks because the interpreters lazy compilation
121	/// compiles one basic block at a time. A malicious program could trigger the compilation
122	/// of the whole program by creating one giant basic block otherwise.
123	pub const BASIC_BLOCK_SIZE: u32 = 1000;
124
125	/// The limit for memory that can be purged on demand.
126	///
127	/// We purge this memory every time we call into another contract.
128	/// Hence we effectively only need to hold it once in RAM.
129	pub const PURGABLE_MEMORY_LIMIT: u32 = INTERPRETER_CACHE_BYTES + 2 * 1024 * 1024;
130
131	/// The limit for memory that needs to be kept alive for a contracts whole life time.
132	///
133	/// This means tuning this number affects the call stack depth.
134	pub const BASELINE_MEMORY_LIMIT: u32 = BLOB_BYTES + 512 * 1024;
135
136	/// Make sure that the various program parts are within the defined limits.
137	pub fn enforce<T: Config>(
138		pvm_blob: Vec<u8>,
139		available_syscalls: &[&[u8]],
140	) -> Result<Vec<u8>, DispatchError> {
141		use polkavm_common::program::{
142			EstimateInterpreterMemoryUsageArgs, ISA_ReviveV1, InstructionSetKind,
143		};
144
145		let len: u64 = pvm_blob.len() as u64;
146		if len > crate::limits::code::BLOB_BYTES.into() {
147			log::debug!(target: LOG_TARGET, "contract blob too large: {len} limit: {BLOB_BYTES}");
148			return Err(<Error<T>>::BlobTooLarge.into());
149		}
150
151		#[cfg(feature = "std")]
152		if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
153			log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
154			return Ok(pvm_blob);
155		}
156
157		let program = polkavm::ProgramBlob::parse(pvm_blob.as_slice().into()).map_err(|err| {
158			log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
159			Error::<T>::CodeRejected
160		})?;
161
162		if !program.is_64_bit() {
163			log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
164			Err(Error::<T>::CodeRejected)?;
165		}
166
167		if program.isa() != InstructionSetKind::ReviveV1 {
168			log::debug!(target: LOG_TARGET, "Program instruction set '{}' is not '{}'", program.isa().name(), InstructionSetKind::ReviveV1.name());
169			Err(Error::<T>::CodeRejected)?;
170		}
171
172		// Need to check that no non-existent syscalls are used. This allows us to add
173		// new syscalls later without affecting already deployed code.
174		for (idx, import) in program.imports().iter().enumerate() {
175			// We are being defensive in case an attacker is able to somehow include
176			// a lot of imports. This is important because we search the array of host
177			// functions for every import.
178			if idx == available_syscalls.len() {
179				log::debug!(target: LOG_TARGET, "Program contains too many imports.");
180				Err(Error::<T>::CodeRejected)?;
181			}
182			let Some(import) = import else {
183				log::debug!(target: LOG_TARGET, "Program contains malformed import.");
184				return Err(Error::<T>::CodeRejected.into());
185			};
186			if !available_syscalls.contains(&import.as_bytes()) {
187				log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
188				Err(Error::<T>::CodeRejected)?;
189			}
190		}
191
192		// This scans the whole program but we only do it once on code deployment.
193		// It is safe to do unchecked math in u32 because the size of the program
194		// was already checked above.
195		let mut max_block_size: u32 = 0;
196		let mut block_size: u32 = 0;
197		let mut basic_block_count: u32 = 0;
198		let mut instruction_count: u32 = 0;
199		for inst in program.instructions_with_isa(ISA_ReviveV1) {
200			use polkavm::program::Instruction;
201			block_size += 1;
202			instruction_count += 1;
203			if inst.kind.opcode().starts_new_basic_block() {
204				max_block_size = max_block_size.max(block_size);
205				block_size = 0;
206				basic_block_count += 1;
207			}
208			match inst.kind {
209				Instruction::invalid => {
210					log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
211					return Err(<Error<T>>::InvalidInstruction.into());
212				},
213				// Since polkavm `0.30.0` linker will fail if it detects sbrk instruction.
214				// So this branch is never reached for programs built with polkavm >= 0.30.0.
215				Instruction::sbrk(_, _) => {
216					log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
217					return Err(<Error<T>>::InvalidInstruction.into());
218				},
219				// Only benchmarking code is allowed to circumvent the import table. We might want
220				// to remove this magic syscall number later. Hence we need to prevent contracts
221				// from using it.
222				#[cfg(not(feature = "runtime-benchmarks"))]
223				Instruction::ecalli(idx) if idx == crate::SENTINEL => {
224					log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
225					return Err(<Error<T>>::InvalidInstruction.into());
226				},
227				_ => (),
228			}
229		}
230		max_block_size = max_block_size.max(block_size);
231
232		if max_block_size > BASIC_BLOCK_SIZE {
233			log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
234			return Err(Error::<T>::BasicBlockTooLarge.into());
235		}
236
237		let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
238			max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
239			instruction_count,
240			max_block_size,
241			basic_block_count,
242			page_size: PAGE_SIZE,
243		};
244
245		let program_info =
246			program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
247				log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
248				Error::<T>::CodeRejected
249			})?;
250
251		log::trace!(
252			target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
253			program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
254			program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
255		);
256
257		if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
258			log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
259				program_info.purgeable_ram_consumption,
260				PURGABLE_MEMORY_LIMIT,
261			);
262			return Err(Error::<T>::StaticMemoryTooLarge.into());
263		}
264
265		if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
266			log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
267				program_info.baseline_ram_consumption,
268				BASELINE_MEMORY_LIMIT,
269			);
270			return Err(Error::<T>::StaticMemoryTooLarge.into());
271		}
272
273		Ok(pvm_blob)
274	}
275}
276
277/// The amount of total memory we require.
278///
279/// Unchecked math is okay since we evaluate at compile time.
280const fn memory_required() -> u32 {
281	// The root frame is not accounted for in CALL_STACK_DEPTH
282	let max_call_depth = CALL_STACK_DEPTH + 1;
283
284	let per_stack_memory = code::PURGABLE_MEMORY_LIMIT + TRANSIENT_STORAGE_BYTES * 2;
285
286	let evm_max_initcode_size = revm::primitives::eip3860::MAX_INITCODE_SIZE as u32;
287	let evm_overhead = EVM_MEMORY_BYTES + evm_max_initcode_size + EVM_STACK_LIMIT * 32;
288	let per_frame_memory = if code::BASELINE_MEMORY_LIMIT > evm_overhead {
289		code::BASELINE_MEMORY_LIMIT
290	} else {
291		evm_overhead
292	} + CALLDATA_BYTES * 2;
293
294	per_stack_memory + max_call_depth * per_frame_memory
295}