pub const CALL_STACK_DEPTH: u32 = 5;
pub const NUM_EVENT_TOPICS: u32 = 4;
pub const PAYLOAD_BYTES: u32 = 416;
pub const TRANSIENT_STORAGE_BYTES: u32 = 4 * 1024;
pub const STORAGE_KEY_BYTES: u32 = 128;
pub const PAGE_SIZE: u32 = 4 * 1024;
pub const IMMUTABLE_BYTES: u32 = 4 * 1024;
pub mod code {
use super::PAGE_SIZE;
use crate::{CodeVec, Config, Error, LOG_TARGET};
use alloc::vec::Vec;
use sp_runtime::DispatchError;
pub const BLOB_BYTES: u32 = 256 * 1024;
pub const STATIC_MEMORY_BYTES: u32 = 2 * 1024 * 1024;
pub const BASIC_BLOCK_SIZE: u32 = 1000;
pub const BYTES_PER_INSTRUCTION: u32 = 20;
const EXTRA_OVERHEAD_PER_CODE_BYTE: u32 = 4;
pub fn enforce<T: Config>(
blob: Vec<u8>,
available_syscalls: &[&[u8]],
) -> Result<CodeVec, DispatchError> {
fn round_page(n: u32) -> u64 {
u64::from(n).next_multiple_of(PAGE_SIZE.into())
}
let blob: CodeVec = blob.try_into().map_err(|_| <Error<T>>::BlobTooLarge)?;
#[cfg(feature = "std")]
if std::env::var_os("REVIVE_SKIP_VALIDATION").is_some() {
log::warn!(target: LOG_TARGET, "Skipping validation because env var REVIVE_SKIP_VALIDATION is set");
return Ok(blob)
}
let program = polkavm::ProgramBlob::parse(blob.as_slice().into()).map_err(|err| {
log::debug!(target: LOG_TARGET, "failed to parse polkavm blob: {err:?}");
Error::<T>::CodeRejected
})?;
if !program.is_64_bit() {
log::debug!(target: LOG_TARGET, "32bit programs are not supported.");
Err(Error::<T>::CodeRejected)?;
}
for (idx, import) in program.imports().iter().enumerate() {
if idx == available_syscalls.len() {
log::debug!(target: LOG_TARGET, "Program contains too many imports.");
Err(Error::<T>::CodeRejected)?;
}
let Some(import) = import else {
log::debug!(target: LOG_TARGET, "Program contains malformed import.");
return Err(Error::<T>::CodeRejected.into());
};
if !available_syscalls.contains(&import.as_bytes()) {
log::debug!(target: LOG_TARGET, "Program references unknown syscall: {}", import);
Err(Error::<T>::CodeRejected)?;
}
}
use polkavm::program::ISA64_V1 as ISA;
let mut num_instructions: u32 = 0;
let mut max_basic_block_size: u32 = 0;
let mut basic_block_size: u32 = 0;
for inst in program.instructions(ISA) {
use polkavm::program::Instruction;
num_instructions += 1;
basic_block_size += 1;
if inst.kind.opcode().starts_new_basic_block() {
max_basic_block_size = max_basic_block_size.max(basic_block_size);
basic_block_size = 0;
}
match inst.kind {
Instruction::invalid => {
log::debug!(target: LOG_TARGET, "invalid instruction at offset {}", inst.offset);
return Err(<Error<T>>::InvalidInstruction.into())
},
Instruction::sbrk(_, _) => {
log::debug!(target: LOG_TARGET, "sbrk instruction is not allowed. offset {}", inst.offset);
return Err(<Error<T>>::InvalidInstruction.into())
},
#[cfg(not(feature = "runtime-benchmarks"))]
Instruction::ecalli(idx) if idx == crate::SENTINEL => {
log::debug!(target: LOG_TARGET, "reserved syscall idx {idx}. offset {}", inst.offset);
return Err(<Error<T>>::InvalidInstruction.into())
},
_ => (),
}
}
if max_basic_block_size > BASIC_BLOCK_SIZE {
log::debug!(target: LOG_TARGET, "basic block too large: {max_basic_block_size} limit: {BASIC_BLOCK_SIZE}");
return Err(Error::<T>::BasicBlockTooLarge.into())
}
let memory_size = (blob.len() as u64)
.saturating_add(round_page(program.ro_data_size()))
.saturating_sub(program.ro_data().len() as u64)
.saturating_add(round_page(program.rw_data_size()))
.saturating_sub(program.rw_data().len() as u64)
.saturating_add(round_page(program.stack_size()))
.saturating_add(
u64::from(num_instructions).saturating_mul(BYTES_PER_INSTRUCTION.into()),
)
.saturating_add(
(program.code().len() as u64).saturating_mul(EXTRA_OVERHEAD_PER_CODE_BYTE.into()),
);
if memory_size > STATIC_MEMORY_BYTES.into() {
log::debug!(target: LOG_TARGET, "static memory too large: {memory_size} limit: {STATIC_MEMORY_BYTES}");
return Err(Error::<T>::StaticMemoryTooLarge.into())
}
Ok(blob)
}
}