1pub const MEMORY_REQUIRED: u32 = memory_required();
39
40pub const CALL_STACK_DEPTH: u32 = 25;
45
46pub const NUM_EVENT_TOPICS: u32 = 4;
50
51pub const MAX_TRANSACTION_PAYLOAD_SIZE: u32 = code::BLOB_BYTES + CALLDATA_BYTES;
55
56pub const STORAGE_BYTES: u32 = 416;
58
59pub const EVENT_BYTES: u32 = 64 * 1024;
61
62pub const EXTRA_EVENT_CHARGE_PER_BYTE: u64 = 256 * 1024;
67
68pub const CALLDATA_BYTES: u32 = 128 * 1024;
72
73pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
77
78pub const STORAGE_KEY_BYTES: u32 = 128;
80
81pub const PAGE_SIZE: u32 = 4 * 1024;
83
84pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
89
90pub const EVM_MEMORY_BYTES: u32 = 1024 * 1024;
92
93pub const EVM_STACK_LIMIT: u32 = 1024;
95
96pub mod code {
104 use super::PAGE_SIZE;
105 use crate::{Config, Error, LOG_TARGET};
106 use alloc::vec::Vec;
107 use sp_runtime::{DispatchError, SaturatedConversion};
108
109 pub const BLOB_BYTES: u32 = 1024 * 1024;
114
115 pub const INTERPRETER_CACHE_BYTES: u32 = 1024 * 1024;
117
118 pub const BASIC_BLOCK_SIZE: u32 = 1000;
124
125 pub const PURGABLE_MEMORY_LIMIT: u32 = INTERPRETER_CACHE_BYTES + 2 * 1024 * 1024;
130
131 pub const BASELINE_MEMORY_LIMIT: u32 = BLOB_BYTES + 512 * 1024;
135
136 #[derive(Debug, PartialEq)]
141 #[non_exhaustive]
142 pub enum CodeRejection {
143 BlobTooLarge { size: u32, limit: u32 },
145 BasicBlockTooLarge { size: u32, limit: u32 },
147 PurgeableMemoryTooLarge { size: u32, limit: u32 },
149 BaselineMemoryTooLarge { size: u32, limit: u32 },
151 InvalidInstruction,
153 Malformed,
156 }
157
158 impl CodeRejection {
159 fn into_error<T: Config>(self) -> Error<T> {
161 match self {
162 Self::BlobTooLarge { .. } => Error::<T>::BlobTooLarge,
163 Self::BasicBlockTooLarge { .. } => Error::<T>::BasicBlockTooLarge,
164 Self::PurgeableMemoryTooLarge { .. } | Self::BaselineMemoryTooLarge { .. } => {
165 Error::<T>::StaticMemoryTooLarge
166 },
167 Self::InvalidInstruction => Error::<T>::InvalidInstruction,
168 Self::Malformed => Error::<T>::CodeRejected,
169 }
170 }
171 }
172
173 pub fn check_pvm_code(pvm_blob: &[u8]) -> Result<(), CodeRejection> {
185 use polkavm_common::program::{
186 EstimateInterpreterMemoryUsageArgs, ISA_ReviveV1, InstructionSetKind,
187 };
188
189 check_blob_size(pvm_blob)?;
190
191 let available_syscalls = crate::vm::pvm::env::list_syscalls();
192
193 let program = polkavm::ProgramBlob::parse(pvm_blob.into()).map_err(|err| {
194 log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
195 CodeRejection::Malformed
196 })?;
197
198 if !program.is_64_bit() {
199 log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
200 Err(CodeRejection::Malformed)?;
201 }
202
203 if program.isa() != InstructionSetKind::ReviveV1 {
204 log::debug!(target: LOG_TARGET, "Program instruction set '{}' is not '{}'", program.isa().name(), InstructionSetKind::ReviveV1.name());
205 Err(CodeRejection::Malformed)?;
206 }
207
208 for (idx, import) in program.imports().iter().enumerate() {
211 if idx == available_syscalls.len() {
215 log::debug!(target: LOG_TARGET, "Program contains too many imports.");
216 Err(CodeRejection::Malformed)?;
217 }
218 let Some(import) = import else {
219 log::debug!(target: LOG_TARGET, "Program contains malformed import.");
220 return Err(CodeRejection::Malformed);
221 };
222 if !available_syscalls.contains(&import.as_bytes()) {
223 log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
224 Err(CodeRejection::Malformed)?;
225 }
226 }
227
228 let mut max_block_size: u32 = 0;
232 let mut block_size: u32 = 0;
233 let mut basic_block_count: u32 = 0;
234 let mut instruction_count: u32 = 0;
235 for inst in program.instructions_with_isa(ISA_ReviveV1) {
236 use polkavm::program::Instruction;
237 block_size += 1;
238 instruction_count += 1;
239 if inst.kind.opcode().starts_new_basic_block() {
240 max_block_size = max_block_size.max(block_size);
241 block_size = 0;
242 basic_block_count += 1;
243 }
244 match inst.kind {
245 Instruction::invalid => {
246 log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
247 return Err(CodeRejection::InvalidInstruction);
248 },
249 Instruction::sbrk(_, _) => {
257 log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
258 return Err(CodeRejection::InvalidInstruction);
259 },
260 #[cfg(not(feature = "runtime-benchmarks"))]
266 Instruction::ecalli(idx) if idx as u32 == crate::SENTINEL => {
267 log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
268 return Err(CodeRejection::InvalidInstruction);
269 },
270 _ => (),
271 }
272 }
273 max_block_size = max_block_size.max(block_size);
274
275 if max_block_size > BASIC_BLOCK_SIZE {
276 log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
277 return Err(CodeRejection::BasicBlockTooLarge {
278 size: max_block_size,
279 limit: BASIC_BLOCK_SIZE,
280 });
281 }
282
283 let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
284 max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
285 instruction_count,
286 max_block_size,
287 basic_block_count,
288 page_size: PAGE_SIZE,
289 };
290
291 let program_info =
292 program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
293 log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
294 CodeRejection::Malformed
295 })?;
296
297 log::trace!(
298 target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
299 program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
300 program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
301 );
302
303 if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
304 log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
305 program_info.purgeable_ram_consumption,
306 PURGABLE_MEMORY_LIMIT,
307 );
308 return Err(CodeRejection::PurgeableMemoryTooLarge {
309 size: program_info.purgeable_ram_consumption,
310 limit: PURGABLE_MEMORY_LIMIT,
311 });
312 }
313
314 if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
315 log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
316 program_info.baseline_ram_consumption,
317 BASELINE_MEMORY_LIMIT,
318 );
319 return Err(CodeRejection::BaselineMemoryTooLarge {
320 size: program_info.baseline_ram_consumption,
321 limit: BASELINE_MEMORY_LIMIT,
322 });
323 }
324
325 Ok(())
326 }
327
328 pub fn enforce<T: Config>(pvm_blob: Vec<u8>) -> Result<Vec<u8>, DispatchError> {
330 #[cfg(feature = "std")]
331 if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
332 check_blob_size(&pvm_blob).map_err(CodeRejection::into_error::<T>)?;
333 log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
334 return Ok(pvm_blob);
335 }
336
337 check_pvm_code(&pvm_blob).map_err(CodeRejection::into_error::<T>)?;
338 Ok(pvm_blob)
339 }
340
341 fn check_blob_size(pvm_blob: &[u8]) -> Result<(), CodeRejection> {
342 let len: u64 = pvm_blob.len() as u64;
343 if len > BLOB_BYTES.into() {
344 log::debug!(target: LOG_TARGET, "contract blob too large: {len} limit: {BLOB_BYTES}");
345 return Err(CodeRejection::BlobTooLarge {
346 size: len.saturated_into(),
347 limit: BLOB_BYTES,
348 });
349 }
350 Ok(())
351 }
352
353 #[cfg(test)]
354 mod tests {
355 use super::CodeRejection;
356 use crate::{Error, tests::Test};
357 use sp_runtime::DispatchError;
358
359 fn to_error(rejection: CodeRejection) -> DispatchError {
360 rejection.into_error::<Test>().into()
361 }
362
363 #[test]
364 fn blob_too_large_maps_to_blob_too_large() {
365 let rejection = CodeRejection::BlobTooLarge { size: 2, limit: 1 };
366 assert_eq!(to_error(rejection), Error::<Test>::BlobTooLarge.into());
367 }
368
369 #[test]
370 fn basic_block_too_large_maps_to_basic_block_too_large() {
371 let rejection = CodeRejection::BasicBlockTooLarge { size: 2, limit: 1 };
372 assert_eq!(to_error(rejection), Error::<Test>::BasicBlockTooLarge.into());
373 }
374
375 #[test]
376 fn purgeable_memory_too_large_maps_to_static_memory_too_large() {
377 let rejection = CodeRejection::PurgeableMemoryTooLarge { size: 2, limit: 1 };
378 assert_eq!(to_error(rejection), Error::<Test>::StaticMemoryTooLarge.into());
379 }
380
381 #[test]
382 fn baseline_memory_too_large_maps_to_static_memory_too_large() {
383 let rejection = CodeRejection::BaselineMemoryTooLarge { size: 2, limit: 1 };
384 assert_eq!(to_error(rejection), Error::<Test>::StaticMemoryTooLarge.into());
385 }
386
387 #[test]
388 fn invalid_instruction_maps_to_invalid_instruction() {
389 assert_eq!(
390 to_error(CodeRejection::InvalidInstruction),
391 Error::<Test>::InvalidInstruction.into()
392 );
393 }
394
395 #[test]
396 fn malformed_maps_to_code_rejected() {
397 assert_eq!(to_error(CodeRejection::Malformed), Error::<Test>::CodeRejected.into());
398 }
399 }
400}
401
402const fn memory_required() -> u32 {
406 let max_call_depth = CALL_STACK_DEPTH + 1;
408
409 let per_stack_memory = code::PURGABLE_MEMORY_LIMIT +
410 TRANSIENT_STORAGE_BYTES * 2 +
411 crate::access_list::MAX_ACCESS_LIST_BYTES;
412
413 let evm_max_initcode_size = revm::primitives::eip3860::MAX_INITCODE_SIZE as u32;
414 let evm_overhead = EVM_MEMORY_BYTES + evm_max_initcode_size + EVM_STACK_LIMIT * 32;
415 let per_frame_memory = if code::BASELINE_MEMORY_LIMIT > evm_overhead {
416 code::BASELINE_MEMORY_LIMIT
417 } else {
418 evm_overhead
419 } + CALLDATA_BYTES * 2;
420
421 per_stack_memory + max_call_depth * per_frame_memory
422}