Newer
Older
// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Environment definition of the wasm smart-contract runtime.
use crate::{
address::AddressMapper,
evm::runtime::GAS_PRICE,
exec::{ExecError, ExecResult, Ext, Key},
gas::{ChargedAmount, Token},
limits,
primitives::ExecReturnValue,
Config, Error, LOG_TARGET, SENTINEL,
};
use alloc::{boxed::Box, vec, vec::Vec};
use codec::{Decode, DecodeLimit, Encode};
use core::{fmt, marker::PhantomData, mem};
use frame_support::{
dispatch::DispatchInfo, ensure, pallet_prelude::DispatchResultWithPostInfo, parameter_types,
traits::Get, weights::Weight,
};
use pallet_revive_proc_macro::define_env;
use pallet_revive_uapi::{CallFlags, ReturnErrorCode, ReturnFlags, StorageFlags};
use sp_core::{H160, H256, U256};
use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256};
use sp_runtime::{DispatchError, RuntimeDebug};
type CallOf<T> = <T as frame_system::Config>::RuntimeCall;
/// The maximum nesting depth a contract can use when encoding types.
const MAX_DECODE_NESTING: u32 = 256;
/// Abstraction over the memory access within syscalls.
///
/// The reason for this abstraction is that we run syscalls on the host machine when
/// benchmarking them. In that case we have direct access to the contract's memory. However, when
/// running within PolkaVM we need to resort to copying as we can't map the contracts memory into
/// the host (as of now).
pub trait Memory<T: Config> {
/// Read designated chunk from the sandbox memory into the supplied buffer.
///
/// Returns `Err` if one of the following conditions occurs:
///
/// - requested buffer is not within the bounds of the sandbox memory.
fn read_into_buf(&self, ptr: u32, buf: &mut [u8]) -> Result<(), DispatchError>;
/// Write the given buffer to the designated location in the sandbox memory.
///
/// Returns `Err` if one of the following conditions occurs:
///
/// - designated area is not within the bounds of the sandbox memory.
fn write(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError>;
/// Zero the designated location in the sandbox memory.
///
/// Returns `Err` if one of the following conditions occurs:
///
/// - designated area is not within the bounds of the sandbox memory.
fn zero(&mut self, ptr: u32, len: u32) -> Result<(), DispatchError>;
/// Read designated chunk from the sandbox memory.
///
/// Returns `Err` if one of the following conditions occurs:
///
/// - requested buffer is not within the bounds of the sandbox memory.
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)
}
/// Same as `read` but reads into a fixed size buffer.
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)
}
/// Read a `u32` from the sandbox memory.
fn read_u32(&self, ptr: u32) -> Result<u32, DispatchError> {
let buf: [u8; 4] = self.read_array(ptr)?;
Ok(u32::from_le_bytes(buf))
}
/// Read a `U256` from the sandbox memory.
fn read_u256(&self, ptr: u32) -> Result<U256, DispatchError> {
let buf: [u8; 32] = self.read_array(ptr)?;
Ok(U256::from_little_endian(&buf))
}
/// Read a `H160` from the sandbox memory.
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)
}
/// Read a `H256` from the sandbox memory.
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)
}
/// Read designated chunk from the sandbox memory and attempt to decode into the specified type.
///
/// Returns `Err` if one of the following conditions occurs:
///
/// - requested buffer is not within the bounds of the sandbox memory.
/// - the buffer contents cannot be decoded as the required type.
///
/// # Note
///
/// Make sure to charge a proportional amount of weight if `len` is not fixed.
fn read_as_unbounded<D: Decode>(&self, ptr: u32, len: u32) -> Result<D, DispatchError> {
let buf = self.read(ptr, len)?;
let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut buf.as_ref())
.map_err(|_| DispatchError::from(Error::<T>::DecodingFailed))?;
Ok(decoded)
}
}
/// Allows syscalls access to the PolkaVM instance they are executing in.
///
/// In case a contract is executing within PolkaVM its `memory` argument will also implement
/// this trait. The benchmarking implementation of syscalls will only require `Memory`
/// to be implemented.
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);
}
// Memory implementation used in benchmarking where guest memory is mapped into the host.
//
// Please note that we could optimize the `read_as_*` functions by decoding directly from
// memory without a copy. However, we don't do that because as it would change the behaviour
// of those functions: A `read_as` with a `len` larger than the actual type can succeed
// in the streaming implementation while it could fail with a segfault in the copy implementation.
#[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])
}
}
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())
}
}
impl<T: Config> PolkaVmInstance<T> for polkavm::RawInstance {
fn gas(&self) -> polkavm::Gas {
self.gas()
}
Loading full blame...