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(_, _) => {
221 log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
222 return Err(<Error<T>>::InvalidInstruction.into());
223 },
224 #[cfg(not(feature = "runtime-benchmarks"))]
230 Instruction::ecalli(idx) if idx as u32 == crate::SENTINEL => {
231 log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
232 return Err(<Error<T>>::InvalidInstruction.into());
233 },
234 _ => (),
235 }
236 }
237 max_block_size = max_block_size.max(block_size);
238
239 if max_block_size > BASIC_BLOCK_SIZE {
240 log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
241 return Err(Error::<T>::BasicBlockTooLarge.into());
242 }
243
244 let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
245 max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
246 instruction_count,
247 max_block_size,
248 basic_block_count,
249 page_size: PAGE_SIZE,
250 };
251
252 let program_info =
253 program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
254 log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
255 Error::<T>::CodeRejected
256 })?;
257
258 log::trace!(
259 target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
260 program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
261 program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
262 );
263
264 if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
265 log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
266 program_info.purgeable_ram_consumption,
267 PURGABLE_MEMORY_LIMIT,
268 );
269 return Err(Error::<T>::StaticMemoryTooLarge.into());
270 }
271
272 if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
273 log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
274 program_info.baseline_ram_consumption,
275 BASELINE_MEMORY_LIMIT,
276 );
277 return Err(Error::<T>::StaticMemoryTooLarge.into());
278 }
279
280 Ok(pvm_blob)
281 }
282}
283
284const fn memory_required() -> u32 {
288 let max_call_depth = CALL_STACK_DEPTH + 1;
290
291 let per_stack_memory = code::PURGABLE_MEMORY_LIMIT +
292 TRANSIENT_STORAGE_BYTES * 2 +
293 crate::access_list::MAX_ACCESS_LIST_BYTES;
294
295 let evm_max_initcode_size = revm::primitives::eip3860::MAX_INITCODE_SIZE as u32;
296 let evm_overhead = EVM_MEMORY_BYTES + evm_max_initcode_size + EVM_STACK_LIMIT * 32;
297 let per_frame_memory = if code::BASELINE_MEMORY_LIMIT > evm_overhead {
298 code::BASELINE_MEMORY_LIMIT
299 } else {
300 evm_overhead
301 } + CALLDATA_BYTES * 2;
302
303 per_stack_memory + max_call_depth * per_frame_memory
304}