pub mod env;
#[cfg(doc)]
pub use env::SyscallDoc;
use crate::{
exec::{CallResources, ExecError, ExecResult, Ext, Key},
limits,
metering::ChargedAmount,
precompiles::{All as AllPrecompiles, Precompiles},
primitives::ExecReturnValue,
Code, Config, Error, Pallet, ReentrancyProtection, RuntimeCosts, LOG_TARGET, SENTINEL,
};
use alloc::{vec, vec::Vec};
use codec::Encode;
use core::{fmt, marker::PhantomData, mem};
use frame_support::{ensure, weights::Weight};
use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags};
use sp_core::{H160, H256, U256};
use sp_runtime::{DispatchError, RuntimeDebug};
pub fn extract_code_and_data(data: &[u8]) -> Option<(Vec<u8>, Vec<u8>)> {
let blob_len = polkavm::ProgramBlob::blob_length(data)?;
let blob_len = blob_len.try_into().ok()?;
let (code, data) = data.split_at_checked(blob_len)?;
Some((code.to_vec(), data.to_vec()))
}
pub trait Memory<T: Config> {
fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError>;
fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>;
fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError>;
fn reset_interpreter_cache(&mut self);
fn read(&self, ptr: u32, len: u32) -> Result<Vec<u8>, DispatchError> {
let mut buf = vec![0u8; len as usize];
self.read_into_buf(ptr, buf.as_mut_slice())?;
Ok(buf)
}
fn read_array<const N: usize>(&self, ptr: u32) -> Result<[u8; N], DispatchError> {
let mut buf = [0u8; N];
self.read_into_buf(ptr, &mut buf)?;
Ok(buf)
}
fn read_u32(&self, ptr: u32) -> Result<u32, DispatchError> {
let buf: [u8; 4] = self.read_array(ptr)?;
Ok(u32::from_le_bytes(buf))
}
fn read_u256(&self, ptr: u32) -> Result<U256, DispatchError> {
let buf: [u8; 32] = self.read_array(ptr)?;
Ok(U256::from_little_endian(&buf))
}
fn read_h160(&self, ptr: u32) -> Result<H160, DispatchError> {
let mut buf = H160::default();
self.read_into_buf(ptr, buf.as_bytes_mut())?;
Ok(buf)
}
fn read_h256(&self, ptr: u32) -> Result<H256, DispatchError> {
let mut code_hash = H256::default();
self.read_into_buf(ptr, code_hash.as_bytes_mut())?;
Ok(code_hash)
}
}
pub trait PolkaVmInstance<T: Config>: Memory<T> {
fn gas(&self) -> polkavm::Gas;
fn set_gas(&mut self, gas: polkavm::Gas);
fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64);
fn write_output(&mut self, output: u64);
}
#[cfg(feature = "runtime-benchmarks")]
impl<T: Config> Memory<T> for [u8] {
fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> {
let ptr = ptr as usize;
let bound_checked =
self.get(ptr..ptr + buf.len()).ok_or_else(|| Error::<T>::OutOfBounds)?;
buf.copy_from_slice(bound_checked);
Ok(())
}
fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> {
let ptr = ptr as usize;
let bound_checked =
self.get_mut(ptr..ptr + buf.len()).ok_or_else(|| Error::<T>::OutOfBounds)?;
bound_checked.copy_from_slice(buf);
Ok(())
}
fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> {
<[u8] as Memory<T>>::write(self, ptr, &vec![0; len as usize])
}
fn reset_interpreter_cache(&mut self) {}
}
impl<T: Config> Memory<T> for polkavm::RawInstance {
fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError> {
self.read_memory_into(ptr, buf)
.map(|_| ())
.map_err(|_| Error::<T>::OutOfBounds.into())
}
fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> {
self.write_memory(ptr, buf).map_err(|_| Error::<T>::OutOfBounds.into())
}
fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError> {
self.zero_memory(ptr, len).map_err(|_| Error::<T>::OutOfBounds.into())
}
fn reset_interpreter_cache(&mut self) {
self.reset_interpreter_cache();
}
}
impl<T: Config> PolkaVmInstance<T> for polkavm::RawInstance {
fn gas(&self) -> polkavm::Gas {
self.gas()
}
fn set_gas(&mut self, gas: polkavm::Gas) {
self.set_gas(gas)
}
fn read_input_regs(&self) -> (u64, u64, u64, u64, u64, u64) {
(
self.reg(polkavm::Reg::A0),
self.reg(polkavm::Reg::A1),
self.reg(polkavm::Reg::A2),
self.reg(polkavm::Reg::A3),
self.reg(polkavm::Reg::A4),
self.reg(polkavm::Reg::A5),
)
}
fn write_output(&mut self, output: u64) {
self.set_reg(polkavm::Reg::A0, output);
}
}
impl From<&ExecReturnValue> for ReturnErrorCode {
fn from(from: &ExecReturnValue) -> Self {
if from.flags.contains(ReturnFlags::REVERT) {
Self::CalleeReverted
} else {
Self::Success
}
}
}
#[derive(RuntimeDebug)]
pub struct ReturnData {
flags: u32,
data: Vec<u8>,
}
#[derive(RuntimeDebug)]
pub enum TrapReason {
SupervisorError(DispatchError),
Return(ReturnData),
Termination,
}
impl<T: Into<DispatchError>> From<T> for TrapReason {
fn from(from: T) -> Self {
Self::SupervisorError(from.into())
}
}
impl fmt::Display for TrapReason {
fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
Ok(())
}
}
macro_rules! charge_gas {
($runtime:expr, $costs:expr) => {{
$runtime.ext.frame_meter_mut().charge_weight_token($costs)
}};
}
enum CallType {
Call { value_ptr: u32 },
DelegateCall,
}
impl CallType {
fn cost(&self) -> RuntimeCosts {
match self {
CallType::Call { .. } => RuntimeCosts::CallBase,
CallType::DelegateCall => RuntimeCosts::DelegateCallBase,
}
}
}
fn already_charged(_: u32) -> Option<RuntimeCosts> {
None
}
fn extract_hi_lo(reg: u64) -> (u32, u32) {
((reg >> 32) as u32, reg as u32)
}
enum StorageValue {
Memory { ptr: u32, len: u32 },
Value(Vec<u8>),
}
enum StorageReadMode {
VariableOutput { output_len_ptr: u32 },
FixedOutput32,
}
pub struct Runtime<'a, E: Ext, M: ?Sized> {
ext: &'a mut E,
input_data: Option<Vec<u8>>,
_phantom_data: PhantomData<M>,
}
impl<'a, E: Ext, M: ?Sized + Memory<E::T>> Runtime<'a, E, M> {
pub fn new(ext: &'a mut E, input_data: Vec<u8>) -> Self {
Self { ext, input_data: Some(input_data), _phantom_data: Default::default() }
}
pub fn ext(&mut self) -> &mut E {
self.ext
}
fn charge_gas(&mut self, costs: RuntimeCosts) -> Result<ChargedAmount, DispatchError> {
charge_gas!(self, costs)
}
fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) {
self.ext.frame_meter_mut().adjust_weight(charged, actual_costs);
}
pub fn write_sandbox_output(
&mut self,
memory: &mut M,
out_ptr: u32,
out_len_ptr: u32,
buf: &[u8],
allow_skip: bool,
create_token: impl FnOnce(u32) -> Option<RuntimeCosts>,
) -> Result<(), DispatchError> {
if allow_skip && out_ptr == SENTINEL {
return Ok(());
}
let len = memory.read_u32(out_len_ptr)?;
let buf_len = len.min(buf.len() as u32);
if let Some(costs) = create_token(buf_len) {
self.charge_gas(costs)?;
}
memory.write(out_ptr, &buf[..buf_len as usize])?;
memory.write(out_len_ptr, &buf_len.encode())
}
pub fn write_fixed_sandbox_output(
&mut self,
memory: &mut M,
out_ptr: u32,
buf: &[u8],
allow_skip: bool,
create_token: impl FnOnce(u32) -> Option<RuntimeCosts>,
) -> Result<(), DispatchError> {
if buf.is_empty() || (allow_skip && out_ptr == SENTINEL) {
return Ok(());
}
let buf_len = buf.len() as u32;
if let Some(costs) = create_token(buf_len) {
self.charge_gas(costs)?;
}
memory.write(out_ptr, buf)
}
fn compute_hash_on_intermediate_buffer<F, R>(
&self,
memory: &mut M,
hash_fn: F,
input_ptr: u32,
input_len: u32,
output_ptr: u32,
) -> Result<(), DispatchError>
where
F: FnOnce(&[u8]) -> R,
R: AsRef<[u8]>,
{
let input = memory.read(input_ptr, input_len)?;
let hash = hash_fn(&input);
memory.write(output_ptr, hash.as_ref())?;
Ok(())
}
fn decode_key(&self, memory: &M, key_ptr: u32, key_len: u32) -> Result<Key, TrapReason> {
let res = match key_len {
SENTINEL => {
let mut buffer = [0u8; 32];
memory.read_into_buf(key_ptr, buffer.as_mut())?;
Ok(Key::from_fixed(buffer))
},
len => {
ensure!(len <= limits::STORAGE_KEY_BYTES, Error::<E::T>::DecodingFailed);
let key = memory.read(key_ptr, len)?;
Key::try_from_var(key)
},
};
res.map_err(|_| Error::<E::T>::DecodingFailed.into())
}
fn is_transient(flags: u32) -> Result<bool, TrapReason> {
StorageFlags::from_bits(flags)
.ok_or_else(|| <Error<E::T>>::InvalidStorageFlags.into())
.map(|flags| flags.contains(StorageFlags::TRANSIENT))
}
fn set_storage(
&mut self,
memory: &M,
flags: u32,
key_ptr: u32,
key_len: u32,
value: StorageValue,
) -> Result<u32, TrapReason> {
let transient = Self::is_transient(flags)?;
let costs = |new_bytes: u32, old_bytes: u32| {
if transient {
RuntimeCosts::SetTransientStorage { new_bytes, old_bytes }
} else {
RuntimeCosts::SetStorage { new_bytes, old_bytes }
}
};
let value_len = match &value {
StorageValue::Memory { ptr: _, len } => *len,
StorageValue::Value(data) => data.len() as u32,
};
let max_size = limits::STORAGE_BYTES;
let charged = self.charge_gas(costs(value_len, max_size))?;
if value_len > max_size {
return Err(Error::<E::T>::ValueTooLarge.into());
}
let key = self.decode_key(memory, key_ptr, key_len)?;
let value = match value {
StorageValue::Memory { ptr, len } => Some(memory.read(ptr, len)?),
StorageValue::Value(data) => Some(data),
};
let write_outcome = if transient {
self.ext.set_transient_storage(&key, value, false)?
} else {
self.ext.set_storage(&key, value, false)?
};
self.adjust_gas(charged, costs(value_len, write_outcome.old_len()));
Ok(write_outcome.old_len_with_sentinel())
}
fn clear_storage(
&mut self,
memory: &M,
flags: u32,
key_ptr: u32,
key_len: u32,
) -> Result<u32, TrapReason> {
let transient = Self::is_transient(flags)?;
let costs = |len| {
if transient {
RuntimeCosts::ClearTransientStorage(len)
} else {
RuntimeCosts::ClearStorage(len)
}
};
let charged = self.charge_gas(costs(limits::STORAGE_BYTES))?;
let key = self.decode_key(memory, key_ptr, key_len)?;
let outcome = if transient {
self.ext.set_transient_storage(&key, None, false)?
} else {
self.ext.set_storage(&key, None, false)?
};
self.adjust_gas(charged, costs(outcome.old_len()));
Ok(outcome.old_len_with_sentinel())
}
fn get_storage(
&mut self,
memory: &mut M,
flags: u32,
key_ptr: u32,
key_len: u32,
out_ptr: u32,
read_mode: StorageReadMode,
) -> Result<ReturnErrorCode, TrapReason> {
let transient = Self::is_transient(flags)?;
let costs = |len| {
if transient {
RuntimeCosts::GetTransientStorage(len)
} else {
RuntimeCosts::GetStorage(len)
}
};
let charged = self.charge_gas(costs(limits::STORAGE_BYTES))?;
let key = self.decode_key(memory, key_ptr, key_len)?;
let outcome = if transient {
self.ext.get_transient_storage(&key)
} else {
self.ext.get_storage(&key)
};
if let Some(value) = outcome {
self.adjust_gas(charged, costs(value.len() as u32));
match read_mode {
StorageReadMode::FixedOutput32 => {
let mut fixed_output = [0u8; 32];
let len = value.len().min(fixed_output.len());
fixed_output[..len].copy_from_slice(&value[..len]);
self.write_fixed_sandbox_output(
memory,
out_ptr,
&fixed_output,
false,
already_charged,
)?;
Ok(ReturnErrorCode::Success)
},
StorageReadMode::VariableOutput { output_len_ptr: out_len_ptr } => {
self.write_sandbox_output(
memory,
out_ptr,
out_len_ptr,
&value,
false,
already_charged,
)?;
Ok(ReturnErrorCode::Success)
},
}
} else {
self.adjust_gas(charged, costs(0));
match read_mode {
StorageReadMode::FixedOutput32 => {
self.write_fixed_sandbox_output(
memory,
out_ptr,
&[0u8; 32],
false,
already_charged,
)?;
Ok(ReturnErrorCode::Success)
},
StorageReadMode::VariableOutput { .. } => Ok(ReturnErrorCode::KeyNotFound),
}
}
}
fn call(
&mut self,
memory: &mut M,
flags: CallFlags,
call_type: CallType,
callee_ptr: u32,
resources: &CallResources<E::T>,
input_data_ptr: u32,
input_data_len: u32,
output_ptr: u32,
output_len_ptr: u32,
) -> Result<ReturnErrorCode, TrapReason> {
let callee = memory.read_h160(callee_ptr)?;
let precompile = <AllPrecompiles<E::T>>::get::<E>(&callee.as_fixed_bytes());
match &precompile {
Some(precompile) if precompile.has_contract_info() =>
self.charge_gas(RuntimeCosts::PrecompileWithInfoBase)?,
Some(_) => self.charge_gas(RuntimeCosts::PrecompileBase)?,
None => self.charge_gas(call_type.cost())?,
};
if input_data_len > limits::CALLDATA_BYTES {
Err(<Error<E::T>>::CallDataTooLarge)?;
}
let input_data = if flags.contains(CallFlags::CLONE_INPUT) {
let input = self.input_data.as_ref().ok_or(Error::<E::T>::InputForwarded)?;
charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?;
input.clone()
} else if flags.contains(CallFlags::FORWARD_INPUT) {
self.input_data.take().ok_or(Error::<E::T>::InputForwarded)?
} else {
if precompile.is_some() {
self.charge_gas(RuntimeCosts::PrecompileDecode(input_data_len))?;
} else {
self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?;
}
memory.read(input_data_ptr, input_data_len)?
};
memory.reset_interpreter_cache();
let call_outcome = match call_type {
CallType::Call { value_ptr } => {
let read_only = flags.contains(CallFlags::READ_ONLY);
let value = memory.read_u256(value_ptr)?;
if value > 0u32.into() {
if read_only || self.ext.is_read_only() {
return Err(Error::<E::T>::StateChangeDenied.into());
}
self.charge_gas(RuntimeCosts::CallTransferSurcharge {
dust_transfer: Pallet::<E::T>::has_dust(value),
})?;
}
let reentrancy = if flags.contains(CallFlags::ALLOW_REENTRY) {
ReentrancyProtection::AllowReentry
} else {
ReentrancyProtection::Strict
};
self.ext.call(resources, &callee, value, input_data, reentrancy, read_only)
},
CallType::DelegateCall => {
if flags.intersects(CallFlags::ALLOW_REENTRY | CallFlags::READ_ONLY) {
return Err(Error::<E::T>::InvalidCallFlags.into());
}
self.ext.delegate_call(resources, callee, input_data)
},
};
match call_outcome {
Ok(_) if flags.contains(CallFlags::TAIL_CALL) => {
let output = mem::take(self.ext.last_frame_output_mut());
return Err(TrapReason::Return(ReturnData {
flags: output.flags.bits(),
data: output.data,
}));
},
Ok(_) => {
let output = mem::take(self.ext.last_frame_output_mut());
let write_result = self.write_sandbox_output(
memory,
output_ptr,
output_len_ptr,
&output.data,
true,
|len| Some(RuntimeCosts::CopyToContract(len)),
);
*self.ext.last_frame_output_mut() = output;
write_result?;
Ok(self.ext.last_frame_output().into())
},
Err(err) => {
let error_code = super::exec_error_into_return_code::<E>(err)?;
memory.write(output_len_ptr, &0u32.to_le_bytes())?;
Ok(error_code)
},
}
}
fn instantiate(
&mut self,
memory: &mut M,
code_hash_ptr: u32,
weight: Weight,
deposit_ptr: u32,
value_ptr: u32,
input_data_ptr: u32,
input_data_len: u32,
address_ptr: u32,
output_ptr: u32,
output_len_ptr: u32,
salt_ptr: u32,
) -> Result<ReturnErrorCode, TrapReason> {
let value = match memory.read_u256(value_ptr) {
Ok(value) => {
self.charge_gas(RuntimeCosts::Instantiate {
input_data_len,
balance_transfer: Pallet::<E::T>::has_balance(value),
dust_transfer: Pallet::<E::T>::has_dust(value),
})?;
value
},
Err(err) => {
self.charge_gas(RuntimeCosts::Instantiate {
input_data_len: 0,
balance_transfer: false,
dust_transfer: false,
})?;
return Err(err.into());
},
};
let deposit_limit: U256 = memory.read_u256(deposit_ptr)?;
let code_hash = memory.read_h256(code_hash_ptr)?;
if input_data_len > limits::CALLDATA_BYTES {
Err(<Error<E::T>>::CallDataTooLarge)?;
}
let input_data = memory.read(input_data_ptr, input_data_len)?;
let salt = if salt_ptr == SENTINEL {
None
} else {
let salt: [u8; 32] = memory.read_array(salt_ptr)?;
Some(salt)
};
memory.reset_interpreter_cache();
match self.ext.instantiate(
&CallResources::from_weight_and_deposit(weight, deposit_limit),
Code::Existing(code_hash),
value,
input_data,
salt.as_ref(),
) {
Ok(address) => {
if !self.ext.last_frame_output().flags.contains(ReturnFlags::REVERT) {
self.write_fixed_sandbox_output(
memory,
address_ptr,
&address.as_bytes(),
true,
already_charged,
)?;
}
let output = mem::take(self.ext.last_frame_output_mut());
let write_result = self.write_sandbox_output(
memory,
output_ptr,
output_len_ptr,
&output.data,
true,
|len| Some(RuntimeCosts::CopyToContract(len)),
);
*self.ext.last_frame_output_mut() = output;
write_result?;
Ok(self.ext.last_frame_output().into())
},
Err(err) => Ok(super::exec_error_into_return_code::<E>(err)?),
}
}
}
pub struct PreparedCall<'a, E: Ext> {
module: polkavm::Module,
instance: polkavm::RawInstance,
runtime: Runtime<'a, E, polkavm::RawInstance>,
}
impl<'a, E: Ext> PreparedCall<'a, E> {
pub fn call(mut self) -> ExecResult {
let exec_result = loop {
let interrupt = self.instance.run();
if let Some(exec_result) =
self.runtime.handle_interrupt(interrupt, &self.module, &mut self.instance)
{
break exec_result
}
};
self.runtime.ext().frame_meter_mut().sync_from_executor(self.instance.gas())?;
exec_result
}
#[cfg(feature = "runtime-benchmarks")]
pub fn aux_data_base(&self) -> u32 {
self.instance.module().memory_map().aux_data_address()
}
#[cfg(feature = "runtime-benchmarks")]
pub fn setup_aux_data(
&mut self,
data: &[u8],
offset: u32,
a1: u64,
) -> frame_support::dispatch::DispatchResult {
let a0 = self.aux_data_base().saturating_add(offset);
self.instance.write_memory(a0, data).map_err(|err| {
log::debug!(target: LOG_TARGET, "failed to write aux data: {err:?}");
Error::<E::T>::CodeRejected
})?;
self.instance.set_reg(polkavm::Reg::A0, a0.into());
self.instance.set_reg(polkavm::Reg::A1, a1);
Ok(())
}
}