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 behaviour version stored with a contract's
22//! code (`CodeInfo::behaviour_version`) is meant for. This is true for either increasing or
23//! decreasing the limit.
24//!
25//! Limits in this file are different from the limits configured on the [`Config`] trait which are
26//! generally only affect actions that cannot be performed by a contract: For example things related
27//! to deposits and weights are allowed to be changed as they are paid by root callers which
28//! are not contracts.
29//!
30//! Exceptions to this rule apply: Limits in the [`code`] module can be increased
31//! without emulating the old values for existing contracts. Reason is that those limits are only
32//! applied **once** at code upload time. Since this action cannot be performed by contracts we
33//! can change those limits without breaking existing contracts. Please keep in mind that we should
34//! only ever **increase** those values but never decrease.
35//!
36//! Exception: [`MAX_RECEIPT_LOGS_BYTES`], see its documentation.
37
38/// The amount of total memory we require to safely operate.
39///
40/// This is not a config knob but derived from the limits in this file.
41pub const MEMORY_REQUIRED: u32 = memory_required();
42
43/// The maximum depth of the call stack.
44///
45/// A 0 means that no callings of other contracts are possible. In other words only the origin
46/// called "root contract" is allowed to execute then.
47pub const CALL_STACK_DEPTH: u32 = 25;
48
49/// The maximum number of topics a call to [`crate::SyscallDoc::deposit_event`] can emit.
50///
51/// We set it to the same limit that ethereum has. It is unlikely to change.
52pub const NUM_EVENT_TOPICS: u32 = 4;
53
54/// Maximum size of an ethereum transaction's RLP encoded payload.
55///
56/// Room for the largest code blob and call data the pallet accepts, plus 64 KiB for the rest of
57/// the transaction: its other fields, signature, access list and authorization list. An
58/// `eth_transact` with a larger payload is rejected as an invalid transaction.
59pub const MAX_TRANSACTION_PAYLOAD_SIZE: u32 = code::BLOB_BYTES + CALLDATA_BYTES + 64 * 1024;
60
61/// Maximum size of storage items.
62pub const STORAGE_BYTES: u32 = 416;
63
64/// Maximum payload size of events.
65pub const EVENT_BYTES: u32 = 64 * 1024;
66
67/// Maximum number of bytes the logs of one ethereum transaction's receipt may take up.
68///
69/// Counted over the RLP encoding of every log the transaction keeps: address, topics and data,
70/// each with its RLP headers. A log that would take the receipt past this limit fails the frame
71/// that emits it with [`crate::Error::ReceiptTooLarge`], and the frame's state changes roll back
72/// as for any other error. This includes a log the runtime mirrors on behalf of a contract inside
73/// a frame. A mirrored log emitted outside any frame, such as while storage deposits are paid,
74/// fails the whole transaction instead. If a storage layer inside the frame or the transaction
75/// records the overflow and is then rolled back, the overflow flag rolls back with that layer's
76/// other changes and fails nothing outside it.
77///
78/// The block's synthetic transaction, which collects logs emitted outside any ethereum
79/// transaction, has the same limit. Nothing fails there: a log that does not fit stays a
80/// substrate-only `ContractEmitted` event.
81///
82/// An Ethereum transaction stays below this limit: at the EIP-7825 cap of 2^24 gas it can log at
83/// most about 2,092,700 bytes, by repeating `LOG0` over the same zeroed memory at 375 gas plus 8
84/// gas per byte. This chain enforces neither that cap nor that `LOG` price, so a transaction here
85/// can reach the limit.
86///
87/// The limit applies to all contracts, including those deployed before it, and is not selected by
88/// a behaviour version. It counts the logs of every contract a transaction calls, and the Ethereum
89/// block builder's memory and storage budget (`BLOCK_BUILDER_BYTES`) is derived from it.
90pub const MAX_RECEIPT_LOGS_BYTES: u32 = 2 * 1024 * 1024;
91
92/// The extra ref time charged per byte of an event's topics (32 bytes each) and data.
93///
94/// It bounds the event bytes (topics and data) a block can deposit; `integrity_test` checks that
95/// this fits the storage available to the runtime. The Ethereum block builder is sized from
96/// [`MAX_TRANSACTION_PAYLOAD_SIZE`] and [`MAX_RECEIPT_LOGS_BYTES`] instead.
97pub const EXTRA_EVENT_CHARGE_PER_BYTE: u64 = 256 * 1024;
98
99/// The maximum size for calldata and return data.
100///
101/// Please note that the calldata is limited to 128KB on geth anyways.
102pub const CALLDATA_BYTES: u32 = 128 * 1024;
103
104/// The maximum size of the transient storage in bytes.
105///
106/// This includes keys, values, and previous entries used for storage rollback.
107pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
108
109/// The maximum allowable length in bytes for (transient) storage keys.
110pub const STORAGE_KEY_BYTES: u32 = 128;
111
112/// The page size in which PolkaVM should allocate memory chunks.
113pub const PAGE_SIZE: u32 = 4 * 1024;
114
115/// The maximum amount of immutable bytes a single contract can store.
116///
117/// The current limit of 4kb allows storing up 16 U256 immutable variables.
118/// Which should always be enough because Solidity allows for 16 local (stack) variables.
119pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
120
121/// upperbound of memory that can be used by the EVM interpreter.
122pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024;
123
124/// EVM interpreter stack limit.
125pub const EVM_STACK_LIMIT: u32 = 1024;
126
127/// Limits that are only enforced on code upload.
128///
129/// # Note
130///
131/// This limit can be increased later without breaking existing contracts
132/// as it is only enforced at code upload time. Code already uploaded
133/// will not be affected by those limits.
134pub mod code {
135 use super::PAGE_SIZE;
136 use crate::{Config, Error, LOG_TARGET};
137 use alloc::vec::Vec;
138 use sp_runtime::{DispatchError, SaturatedConversion};
139
140 /// The maximum length of a code blob in bytes.
141 ///
142 /// This mostly exist to prevent parsing too big blobs and to
143 /// have a maximum encoded length.
144 pub const BLOB_BYTES: u32 = 1024 * 1024;
145
146 /// The maximum amount of memory the interpreter is allowed to use for compilation artifacts.
147 pub const INTERPRETER_CACHE_BYTES: u32 = 1024 * 1024;
148
149 /// The maximum size of a basic block in number of instructions.
150 ///
151 /// We need to limit the size of basic blocks because the interpreters lazy compilation
152 /// compiles one basic block at a time. A malicious program could trigger the compilation
153 /// of the whole program by creating one giant basic block otherwise.
154 pub const BASIC_BLOCK_SIZE: u32 = 1000;
155
156 /// The limit for memory that can be purged on demand.
157 ///
158 /// It is the interpreter cache and the map of compiled blocks of a PVM frame. When a PVM
159 /// frame starts, the innermost PVM frame that waits on a sub-call purges it. Hence we
160 /// effectively only need to hold it once in RAM.
161 pub const PURGABLE_MEMORY_LIMIT: u32 = INTERPRETER_CACHE_BYTES + 2 * 1024 * 1024;
162
163 /// The limit for memory that needs to be kept alive for a contracts whole life time.
164 ///
165 /// This means tuning this number affects the call stack depth.
166 pub const BASELINE_MEMORY_LIMIT: u32 = BLOB_BYTES + 512 * 1024;
167
168 /// Why [`check_pvm_code`] rejected a code blob.
169 ///
170 /// The limits that were exceeded are reported alongside the measured value. They are not
171 /// meant to be hard-coded by tooling since they may be raised in future releases.
172 #[derive(Debug, PartialEq)]
173 #[non_exhaustive]
174 pub enum CodeRejection {
175 /// The blob is larger than allowed. Sizes are in bytes.
176 BlobTooLarge { size: u32, limit: u32 },
177 /// A basic block is larger than allowed. Sizes are in number of instructions.
178 BasicBlockTooLarge { size: u32, limit: u32 },
179 /// The program needs more purgeable memory than allowed. Sizes are in bytes.
180 PurgeableMemoryTooLarge { size: u32, limit: u32 },
181 /// The program needs more baseline memory than allowed. Sizes are in bytes.
182 BaselineMemoryTooLarge { size: u32, limit: u32 },
183 /// The program contains an instruction that contracts are not allowed to use.
184 InvalidInstruction,
185 /// The blob failed to parse, is a 32 bit program, uses the wrong instruction set, or
186 /// contains a malformed or unknown import.
187 Malformed,
188 }
189
190 impl CodeRejection {
191 /// The error the pallet returns on code upload for this rejection.
192 fn into_error<T: Config>(self) -> Error<T> {
193 match self {
194 Self::BlobTooLarge { .. } => Error::<T>::BlobTooLarge,
195 Self::BasicBlockTooLarge { .. } => Error::<T>::BasicBlockTooLarge,
196 Self::PurgeableMemoryTooLarge { .. } | Self::BaselineMemoryTooLarge { .. } => {
197 Error::<T>::StaticMemoryTooLarge
198 },
199 Self::InvalidInstruction => Error::<T>::InvalidInstruction,
200 Self::Malformed => Error::<T>::CodeRejected,
201 }
202 }
203 }
204
205 /// Make sure that the various program parts are within the defined limits.
206 ///
207 /// This is the check the pallet runs on code upload. It is not generic over the runtime so
208 /// that tooling can find out ahead of deployment whether a blob would be accepted.
209 ///
210 /// The verdict only matches a production runtime when `pallet-revive` is built without the
211 /// `runtime-benchmarks` feature. With it, a blob that uses the reserved `SENTINEL` ecalli
212 /// passes here but is rejected with [`CodeRejection::InvalidInstruction`] on upload, and a
213 /// blob that imports the benchmark-only `noop` syscall passes here but is rejected with
214 /// [`CodeRejection::Malformed`]. Features unify across the build, so a dependency can
215 /// enable it unasked.
216 pub fn check_pvm_code(pvm_blob: &[u8]) -> Result<(), CodeRejection> {
217 use polkavm_common::program::{
218 EstimateInterpreterMemoryUsageArgs, ISA_ReviveV1, InstructionSetKind,
219 };
220
221 check_blob_size(pvm_blob)?;
222
223 let available_syscalls = crate::vm::pvm::env::list_syscalls();
224
225 let program = polkavm::ProgramBlob::parse(pvm_blob.into()).map_err(|err| {
226 log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
227 CodeRejection::Malformed
228 })?;
229
230 if !program.is_64_bit() {
231 log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
232 Err(CodeRejection::Malformed)?;
233 }
234
235 if program.isa() != InstructionSetKind::ReviveV1 {
236 log::debug!(target: LOG_TARGET, "Program instruction set '{}' is not '{}'", program.isa().name(), InstructionSetKind::ReviveV1.name());
237 Err(CodeRejection::Malformed)?;
238 }
239
240 // Need to check that no non-existent syscalls are used. This allows us to add
241 // new syscalls later without affecting already deployed code.
242 for (idx, import) in program.imports().iter().enumerate() {
243 // We are being defensive in case an attacker is able to somehow include
244 // a lot of imports. This is important because we search the array of host
245 // functions for every import.
246 if idx == available_syscalls.len() {
247 log::debug!(target: LOG_TARGET, "Program contains too many imports.");
248 Err(CodeRejection::Malformed)?;
249 }
250 let Some(import) = import else {
251 log::debug!(target: LOG_TARGET, "Program contains malformed import.");
252 return Err(CodeRejection::Malformed);
253 };
254 if !available_syscalls.contains(&import.as_bytes()) {
255 log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
256 Err(CodeRejection::Malformed)?;
257 }
258 }
259
260 // This scans the whole program but we only do it once on code deployment.
261 // It is safe to do unchecked math in u32 because the size of the program
262 // was already checked above.
263 let mut max_block_size: u32 = 0;
264 let mut block_size: u32 = 0;
265 let mut basic_block_count: u32 = 0;
266 let mut instruction_count: u32 = 0;
267 for inst in program.instructions_with_isa(ISA_ReviveV1) {
268 use polkavm::program::Instruction;
269 block_size += 1;
270 instruction_count += 1;
271 if inst.kind.opcode().starts_new_basic_block() {
272 max_block_size = max_block_size.max(block_size);
273 block_size = 0;
274 basic_block_count += 1;
275 }
276 match inst.kind {
277 Instruction::invalid => {
278 log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
279 return Err(CodeRejection::InvalidInstruction);
280 },
281 // Since polkavm `0.30.0` linker will fail if it detects sbrk instruction.
282 // So this branch is never reached for programs built with polkavm >= 0.30.0.
283 //
284 // # Note
285 //
286 // The decoder might decode sbrk to `invalid` under our Revive isa. In this case,
287 // this branch is _not_ hit for sbrk but the one above instead.
288 Instruction::sbrk(_, _) => {
289 log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
290 return Err(CodeRejection::InvalidInstruction);
291 },
292 // Only benchmarking code is allowed to circumvent the import table. We might want
293 // to remove this magic syscall number later. Hence we need to prevent contracts
294 // from using it.
295 //
296 // The `as` conversion is fine here because we are just testing equality.
297 #[cfg(not(feature = "runtime-benchmarks"))]
298 Instruction::ecalli(idx) if idx as u32 == crate::SENTINEL => {
299 log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
300 return Err(CodeRejection::InvalidInstruction);
301 },
302 _ => (),
303 }
304 }
305 max_block_size = max_block_size.max(block_size);
306
307 if max_block_size > BASIC_BLOCK_SIZE {
308 log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
309 return Err(CodeRejection::BasicBlockTooLarge {
310 size: max_block_size,
311 limit: BASIC_BLOCK_SIZE,
312 });
313 }
314
315 let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
316 max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
317 instruction_count,
318 max_block_size,
319 basic_block_count,
320 page_size: PAGE_SIZE,
321 };
322
323 let program_info =
324 program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
325 log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
326 CodeRejection::Malformed
327 })?;
328
329 log::trace!(
330 target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
331 program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
332 program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
333 );
334
335 if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
336 log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
337 program_info.purgeable_ram_consumption,
338 PURGABLE_MEMORY_LIMIT,
339 );
340 return Err(CodeRejection::PurgeableMemoryTooLarge {
341 size: program_info.purgeable_ram_consumption,
342 limit: PURGABLE_MEMORY_LIMIT,
343 });
344 }
345
346 if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
347 log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
348 program_info.baseline_ram_consumption,
349 BASELINE_MEMORY_LIMIT,
350 );
351 return Err(CodeRejection::BaselineMemoryTooLarge {
352 size: program_info.baseline_ram_consumption,
353 limit: BASELINE_MEMORY_LIMIT,
354 });
355 }
356
357 Ok(())
358 }
359
360 /// Runs [`check_pvm_code`] and turns a rejection into the pallet's dispatch error.
361 pub fn enforce<T: Config>(pvm_blob: Vec<u8>) -> Result<Vec<u8>, DispatchError> {
362 #[cfg(feature = "std")]
363 if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
364 check_blob_size(&pvm_blob).map_err(CodeRejection::into_error::<T>)?;
365 log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
366 return Ok(pvm_blob);
367 }
368
369 check_pvm_code(&pvm_blob).map_err(CodeRejection::into_error::<T>)?;
370 Ok(pvm_blob)
371 }
372
373 fn check_blob_size(pvm_blob: &[u8]) -> Result<(), CodeRejection> {
374 let len: u64 = pvm_blob.len() as u64;
375 if len > BLOB_BYTES.into() {
376 log::debug!(target: LOG_TARGET, "contract blob too large: {len} limit: {BLOB_BYTES}");
377 return Err(CodeRejection::BlobTooLarge {
378 size: len.saturated_into(),
379 limit: BLOB_BYTES,
380 });
381 }
382 Ok(())
383 }
384
385 #[cfg(test)]
386 mod tests {
387 use super::CodeRejection;
388 use crate::{Error, tests::Test};
389 use sp_runtime::DispatchError;
390
391 fn to_error(rejection: CodeRejection) -> DispatchError {
392 rejection.into_error::<Test>().into()
393 }
394
395 #[test]
396 fn blob_too_large_maps_to_blob_too_large() {
397 let rejection = CodeRejection::BlobTooLarge { size: 2, limit: 1 };
398 assert_eq!(to_error(rejection), Error::<Test>::BlobTooLarge.into());
399 }
400
401 #[test]
402 fn basic_block_too_large_maps_to_basic_block_too_large() {
403 let rejection = CodeRejection::BasicBlockTooLarge { size: 2, limit: 1 };
404 assert_eq!(to_error(rejection), Error::<Test>::BasicBlockTooLarge.into());
405 }
406
407 #[test]
408 fn purgeable_memory_too_large_maps_to_static_memory_too_large() {
409 let rejection = CodeRejection::PurgeableMemoryTooLarge { size: 2, limit: 1 };
410 assert_eq!(to_error(rejection), Error::<Test>::StaticMemoryTooLarge.into());
411 }
412
413 #[test]
414 fn baseline_memory_too_large_maps_to_static_memory_too_large() {
415 let rejection = CodeRejection::BaselineMemoryTooLarge { size: 2, limit: 1 };
416 assert_eq!(to_error(rejection), Error::<Test>::StaticMemoryTooLarge.into());
417 }
418
419 #[test]
420 fn invalid_instruction_maps_to_invalid_instruction() {
421 assert_eq!(
422 to_error(CodeRejection::InvalidInstruction),
423 Error::<Test>::InvalidInstruction.into()
424 );
425 }
426
427 #[test]
428 fn malformed_maps_to_code_rejected() {
429 assert_eq!(to_error(CodeRejection::Malformed), Error::<Test>::CodeRejected.into());
430 }
431 }
432}
433
434/// The amount of total memory we require.
435///
436/// Unchecked math is okay since we evaluate at compile time.
437const fn memory_required() -> u32 {
438 // The root frame is not accounted for in CALL_STACK_DEPTH
439 let max_call_depth = CALL_STACK_DEPTH + 1;
440
441 let per_stack_memory = code::PURGABLE_MEMORY_LIMIT +
442 TRANSIENT_STORAGE_BYTES * 2 +
443 crate::access_list::MAX_ACCESS_LIST_BYTES;
444
445 let evm_max_initcode_size = revm::primitives::eip3860::MAX_INITCODE_SIZE as u32;
446 let evm_overhead = EVM_MEMORY_BYTES + evm_max_initcode_size + EVM_STACK_LIMIT * 32;
447 let per_frame_memory = if code::BASELINE_MEMORY_LIMIT > evm_overhead {
448 code::BASELINE_MEMORY_LIMIT
449 } else {
450 evm_overhead
451 } + CALLDATA_BYTES * 2;
452
453 per_stack_memory + max_call_depth * per_frame_memory
454}