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;
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 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 for (idx, import) in program.imports().iter().enumerate() {
175 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 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 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 #[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
277const fn memory_required() -> u32 {
281 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}