mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 15:11:02 +00:00
contracts: Allow runtime authors to define a chain extension (#7548)
* Make host functions return TrapReason This avoids the need to manually store any trap reasons to the `Runtime` from the host function. This adds the following benefits: * It properly composes with the upcoming chain extensions * Missing to set a trap value is now a compile error * Add chain extension The chain extension is a way for the contract author to add new host functions for contracts to call. * Add tests for chain extensions * Fix regression in set_rent.wat fixture Not all offsets where properly updated when changing the fixtures for the new salt on instantiate. * Pre-charge a weight amount based off the specified length * Improve fn write docs * Renamed state to phantom * Fix typo
This commit is contained in:
committed by
GitHub
parent
e3e651f72c
commit
51c37ecc15
@@ -20,7 +20,7 @@
|
||||
use crate::{
|
||||
HostFnWeights, Schedule, Config, CodeHash, BalanceOf, Error,
|
||||
exec::{Ext, StorageKey, TopicOf},
|
||||
gas::{Gas, GasMeter, Token, GasMeterResult},
|
||||
gas::{Gas, GasMeter, Token, GasMeterResult, ChargedAmount},
|
||||
wasm::env_def::ConvertibleToWasm,
|
||||
};
|
||||
use sp_sandbox;
|
||||
@@ -28,7 +28,7 @@ use parity_wasm::elements::ValueType;
|
||||
use frame_system;
|
||||
use frame_support::dispatch::DispatchError;
|
||||
use sp_std::prelude::*;
|
||||
use codec::{Decode, Encode};
|
||||
use codec::{Decode, DecodeAll, Encode};
|
||||
use sp_runtime::traits::SaturatedConversion;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use sp_io::hashing::{
|
||||
@@ -126,7 +126,7 @@ impl<T: Into<DispatchError>> From<T> for TrapReason {
|
||||
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Copy, Clone)]
|
||||
enum RuntimeToken {
|
||||
pub enum RuntimeToken {
|
||||
/// Charge the gas meter with the cost of a metering block. The charged costs are
|
||||
/// the supplied cost of the block plus the overhead of the metering itself.
|
||||
MeteringBlock(u32),
|
||||
@@ -198,6 +198,10 @@ enum RuntimeToken {
|
||||
HashBlake256(u32),
|
||||
/// Weight of calling `seal_hash_blake2_128` for the given input size.
|
||||
HashBlake128(u32),
|
||||
/// Weight charged by a chain extension through `seal_call_chain_extension`.
|
||||
ChainExtension(u64),
|
||||
/// Weight charged for copying data from the sandbox.
|
||||
CopyIn(u32),
|
||||
}
|
||||
|
||||
impl<T: Config> Token<T> for RuntimeToken
|
||||
@@ -256,6 +260,8 @@ where
|
||||
.saturating_add(s.hash_blake2_256_per_byte.saturating_mul(len.into())),
|
||||
HashBlake128(len) => s.hash_blake2_128
|
||||
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
||||
ChainExtension(amount) => amount,
|
||||
CopyIn(len) => s.return_per_byte.saturating_mul(len.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,6 +382,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the inner `Ext`.
|
||||
///
|
||||
/// This is mainly for the chain extension to have access to the environment the
|
||||
/// contract is executing in.
|
||||
pub fn ext(&mut self) -> &mut E {
|
||||
self.ext
|
||||
}
|
||||
|
||||
/// Store the reason for a host function triggered trap.
|
||||
///
|
||||
/// This is called by the `define_env` macro in order to store any error returned by
|
||||
@@ -388,12 +402,12 @@ where
|
||||
/// Charge the gas meter with the specified token.
|
||||
///
|
||||
/// Returns `Err(HostError)` if there is not enough gas.
|
||||
fn charge_gas<Tok>(&mut self, token: Tok) -> Result<(), DispatchError>
|
||||
pub fn charge_gas<Tok>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError>
|
||||
where
|
||||
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
|
||||
{
|
||||
match self.gas_meter.charge(&self.schedule.host_fn_weights, token) {
|
||||
GasMeterResult::Proceed => Ok(()),
|
||||
GasMeterResult::Proceed(amount) => Ok(amount),
|
||||
GasMeterResult::OutOfGas => Err(Error::<E::T>::OutOfGas.into())
|
||||
}
|
||||
}
|
||||
@@ -403,7 +417,7 @@ where
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory(&self, ptr: u32, len: u32)
|
||||
pub fn read_sandbox_memory(&self, ptr: u32, len: u32)
|
||||
-> Result<Vec<u8>, DispatchError>
|
||||
{
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
@@ -417,7 +431,7 @@ where
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory_into_buf(&self, ptr: u32, buf: &mut [u8])
|
||||
pub fn read_sandbox_memory_into_buf(&self, ptr: u32, buf: &mut [u8])
|
||||
-> Result<(), DispatchError>
|
||||
{
|
||||
self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
|
||||
@@ -429,20 +443,29 @@ where
|
||||
///
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
/// - the buffer contents cannot be decoded as the required type.
|
||||
fn read_sandbox_memory_as<D: Decode>(&self, ptr: u32, len: u32)
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// It is safe to forgo benchmarking and charging weight relative to `len` for fixed
|
||||
/// size types (basically everything not containing a heap collection):
|
||||
/// Despite the fact that we are usually about to read the encoding of a fixed size
|
||||
/// type, we cannot know the encoded size of that type. We therefore are required to
|
||||
/// use the length provided by the contract. This length is untrusted and therefore
|
||||
/// we charge weight relative to the provided size upfront that covers the copy costs.
|
||||
/// On success this cost is refunded as the copying was already covered in the
|
||||
/// overall cost of the host function. This is different from `read_sandbox_memory`
|
||||
/// where the size is dynamic and the costs resulting from that dynamic size must
|
||||
/// be charged relative to this dynamic size anyways (before reading) by constructing
|
||||
/// the benchmark for that.
|
||||
pub fn read_sandbox_memory_as<D: Decode>(&mut self, ptr: u32, len: u32)
|
||||
-> Result<D, DispatchError>
|
||||
{
|
||||
let amount = self.charge_gas(RuntimeToken::CopyIn(len))?;
|
||||
let buf = self.read_sandbox_memory(ptr, len)?;
|
||||
D::decode(&mut &buf[..]).map_err(|_| Error::<E::T>::DecodingFailed.into())
|
||||
}
|
||||
|
||||
/// 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_sandbox_memory(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> {
|
||||
self.memory.set(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
|
||||
let decoded = D::decode_all(&mut &buf[..])
|
||||
.map_err(|_| DispatchError::from(Error::<E::T>::DecodingFailed))?;
|
||||
self.gas_meter.refund(amount);
|
||||
Ok(decoded)
|
||||
}
|
||||
|
||||
/// Write the given buffer and its length to the designated locations in sandbox memory and
|
||||
@@ -464,7 +487,7 @@ where
|
||||
///
|
||||
/// In addition to the error conditions of `write_sandbox_memory` this functions returns
|
||||
/// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`.
|
||||
fn write_sandbox_output(
|
||||
pub fn write_sandbox_output(
|
||||
&mut self,
|
||||
out_ptr: u32,
|
||||
out_len_ptr: u32,
|
||||
@@ -496,6 +519,15 @@ where
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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_sandbox_memory(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> {
|
||||
self.memory.set(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
|
||||
}
|
||||
|
||||
/// Computes the given hash function on the supplied input.
|
||||
///
|
||||
/// Reads from the sandboxed input buffer into an intermediate buffer.
|
||||
@@ -1362,4 +1394,37 @@ define_env!(Env, <E: Ext>,
|
||||
ctx.charge_gas(RuntimeToken::HashBlake128(input_len))?;
|
||||
Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?)
|
||||
},
|
||||
|
||||
// Call into the chain extension provided by the chain if any.
|
||||
//
|
||||
// Handling of the input values is up to the specific chain extension and so is the
|
||||
// return value. The extension can decide to use the inputs as primitive inputs or as
|
||||
// in/out arguments by interpreting them as pointers. Any caller of this function
|
||||
// must therefore coordinate with the chain that it targets.
|
||||
//
|
||||
// # Note
|
||||
//
|
||||
// If no chain extension exists the contract will trap with the `NoChainExtension`
|
||||
// module error.
|
||||
seal_call_chain_extension(
|
||||
ctx,
|
||||
func_id: u32,
|
||||
input_ptr: u32,
|
||||
input_len: u32,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32
|
||||
) -> u32 => {
|
||||
use crate::chain_extension::{ChainExtension, Environment, RetVal};
|
||||
if <E::T as Config>::ChainExtension::enabled() == false {
|
||||
Err(Error::<E::T>::NoChainExtension)?;
|
||||
}
|
||||
let env = Environment::new(ctx, input_ptr, input_len, output_ptr, output_len_ptr);
|
||||
match <E::T as Config>::ChainExtension::call(func_id, env)? {
|
||||
RetVal::Converging(val) => Ok(val),
|
||||
RetVal::Diverging{flags, data} => Err(TrapReason::Return(ReturnData {
|
||||
flags: flags.bits(),
|
||||
data,
|
||||
})),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user