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 PAYLOAD_BYTES: u32 = 416;
53
54pub const CALLDATA_BYTES: u32 = 128 * 1024;
58
59pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
63
64pub const STORAGE_KEY_BYTES: u32 = 128;
66
67pub const PAGE_SIZE: u32 = 4 * 1024;
69
70pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
75
76pub mod code {
84 use super::PAGE_SIZE;
85 use crate::{Config, Error, LOG_TARGET};
86 use alloc::vec::Vec;
87 use sp_runtime::DispatchError;
88
89 pub const BLOB_BYTES: u32 = 1024 * 1024;
94
95 pub const INTERPRETER_CACHE_BYTES: u32 = 1024 * 1024;
97
98 pub const BASIC_BLOCK_SIZE: u32 = 1000;
104
105 pub const PURGABLE_MEMORY_LIMIT: u32 = INTERPRETER_CACHE_BYTES + 2 * 1024 * 1024;
110
111 pub const BASELINE_MEMORY_LIMIT: u32 = BLOB_BYTES + 512 * 1024;
115
116 pub fn enforce<T: Config>(
118 pvm_blob: Vec<u8>,
119 available_syscalls: &[&[u8]],
120 ) -> Result<Vec<u8>, DispatchError> {
121 use polkavm::program::ISA64_V1 as ISA;
122 use polkavm_common::program::EstimateInterpreterMemoryUsageArgs;
123
124 let len: u64 = pvm_blob.len() as u64;
125 if len > crate::limits::code::BLOB_BYTES.into() {
126 log::debug!(target: LOG_TARGET, "contract blob too large: {len} limit: {BLOB_BYTES}");
127 return Err(<Error<T>>::BlobTooLarge.into())
128 }
129
130 #[cfg(feature = "std")]
131 if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
132 log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
133 return Ok(pvm_blob)
134 }
135
136 let program = polkavm::ProgramBlob::parse(pvm_blob.as_slice().into()).map_err(|err| {
137 log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
138 Error::<T>::CodeRejected
139 })?;
140
141 if !program.is_64_bit() {
142 log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
143 Err(Error::<T>::CodeRejected)?;
144 }
145
146 for (idx, import) in program.imports().iter().enumerate() {
149 if idx == available_syscalls.len() {
153 log::debug!(target: LOG_TARGET, "Program contains too many imports.");
154 Err(Error::<T>::CodeRejected)?;
155 }
156 let Some(import) = import else {
157 log::debug!(target: LOG_TARGET, "Program contains malformed import.");
158 return Err(Error::<T>::CodeRejected.into());
159 };
160 if !available_syscalls.contains(&import.as_bytes()) {
161 log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
162 Err(Error::<T>::CodeRejected)?;
163 }
164 }
165
166 let mut max_block_size: u32 = 0;
170 let mut block_size: u32 = 0;
171 let mut basic_block_count: u32 = 0;
172 let mut instruction_count: u32 = 0;
173 for inst in program.instructions(ISA) {
174 use polkavm::program::Instruction;
175 block_size += 1;
176 instruction_count += 1;
177 if inst.kind.opcode().starts_new_basic_block() {
178 max_block_size = max_block_size.max(block_size);
179 block_size = 0;
180 basic_block_count += 1;
181 }
182 match inst.kind {
183 Instruction::invalid => {
184 log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
185 return Err(<Error<T>>::InvalidInstruction.into())
186 },
187 Instruction::sbrk(_, _) => {
188 log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
189 return Err(<Error<T>>::InvalidInstruction.into())
190 },
191 #[cfg(not(feature = "runtime-benchmarks"))]
195 Instruction::ecalli(idx) if idx == crate::SENTINEL => {
196 log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
197 return Err(<Error<T>>::InvalidInstruction.into())
198 },
199 _ => (),
200 }
201 }
202 max_block_size = max_block_size.max(block_size);
203
204 if max_block_size > BASIC_BLOCK_SIZE {
205 log::debug!(target: LOG_TARGET, "basic block too large: {max_block_size} limit: {BASIC_BLOCK_SIZE}");
206 return Err(Error::<T>::BasicBlockTooLarge.into())
207 }
208
209 let usage_args = EstimateInterpreterMemoryUsageArgs::BoundedCache {
210 max_cache_size_bytes: INTERPRETER_CACHE_BYTES,
211 instruction_count,
212 max_block_size,
213 basic_block_count,
214 page_size: PAGE_SIZE,
215 };
216
217 let program_info =
218 program.estimate_interpreter_memory_usage(usage_args).map_err(|err| {
219 log::debug!(target: LOG_TARGET, "failed to estimate memory usage of program: {err:?}");
220 Error::<T>::CodeRejected
221 })?;
222
223 log::trace!(
224 target: LOG_TARGET, "Contract memory usage: purgable={}/{} KB baseline={}/{}",
225 program_info.purgeable_ram_consumption, PURGABLE_MEMORY_LIMIT,
226 program_info.baseline_ram_consumption, BASELINE_MEMORY_LIMIT,
227 );
228
229 if program_info.purgeable_ram_consumption > PURGABLE_MEMORY_LIMIT {
230 log::debug!(target: LOG_TARGET, "contract uses too much purgeable memory: {} limit: {}",
231 program_info.purgeable_ram_consumption,
232 PURGABLE_MEMORY_LIMIT,
233 );
234 return Err(Error::<T>::StaticMemoryTooLarge.into())
235 }
236
237 if program_info.baseline_ram_consumption > BASELINE_MEMORY_LIMIT {
238 log::debug!(target: LOG_TARGET, "contract uses too much baseline memory: {} limit: {}",
239 program_info.baseline_ram_consumption,
240 BASELINE_MEMORY_LIMIT,
241 );
242 return Err(Error::<T>::StaticMemoryTooLarge.into())
243 }
244
245 Ok(pvm_blob)
246 }
247}
248
249const fn memory_required() -> u32 {
253 let max_call_depth = CALL_STACK_DEPTH + 1;
255
256 let per_stack_memory = code::PURGABLE_MEMORY_LIMIT + TRANSIENT_STORAGE_BYTES * 2;
257 let per_frame_memory = code::BASELINE_MEMORY_LIMIT + CALLDATA_BYTES * 2;
258
259 per_stack_memory + max_call_depth * per_frame_memory
260}