contracts: Change define_env! to expect a Result<T, DispatchError> for every function (#7762)

* 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

* review: Remove superflous .into()
This commit is contained in:
Alexander Theißen
2020-12-29 13:58:23 +01:00
committed by GitHub
parent dd8e7587cb
commit ab876be9e9
5 changed files with 146 additions and 156 deletions
+8
View File
@@ -378,6 +378,14 @@ decl_error! {
/// on the call stack. Those actions are contract self destruction and restoration /// on the call stack. Those actions are contract self destruction and restoration
/// of a tombstone. /// of a tombstone.
ReentranceDenied, ReentranceDenied,
/// `seal_input` was called twice from the same contract execution context.
InputAlreadyRead,
/// The subject passed to `seal_random` exceeds the limit.
RandomSubjectTooLong,
/// The amount of topics passed to `seal_deposit_events` exceeds the limit.
TooManyTopics,
/// The topics passed to `seal_deposit_events` contains at least one duplicate.
DuplicateTopics,
} }
} }
@@ -96,7 +96,7 @@ macro_rules! unmarshall_then_body {
#[inline(always)] #[inline(always)]
pub fn constrain_closure<R, F>(f: F) -> F pub fn constrain_closure<R, F>(f: F) -> F
where where
F: FnOnce() -> Result<R, sp_sandbox::HostError>, F: FnOnce() -> Result<R, crate::wasm::runtime::TrapReason>,
{ {
f f
} }
@@ -109,14 +109,20 @@ macro_rules! unmarshall_then_body_then_marshall {
>(|| { >(|| {
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
}); });
let r = body()?; let r = body().map_err(|reason| {
$ctx.set_trap_reason(reason);
sp_sandbox::HostError
})?;
return Ok(sp_sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() })) return Ok(sp_sandbox::ReturnValue::Value({ use $crate::wasm::env_def::ConvertibleToWasm; r.to_typed_value() }))
}); });
( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({ ( $args_iter:ident, $ctx:ident, ( $( $names:ident : $params:ty ),* ) => $body:tt ) => ({
let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| { let body = $crate::wasm::env_def::macros::constrain_closure::<(), _>(|| {
unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*) unmarshall_then_body!($body, $ctx, $args_iter, $( $names : $params ),*)
}); });
body()?; body().map_err(|reason| {
$ctx.set_trap_reason(reason);
sp_sandbox::HostError
})?;
return Ok(sp_sandbox::ReturnValue::Unit) return Ok(sp_sandbox::ReturnValue::Unit)
}) })
} }
@@ -207,15 +213,24 @@ mod tests {
use parity_wasm::elements::ValueType; use parity_wasm::elements::ValueType;
use sp_runtime::traits::Zero; use sp_runtime::traits::Zero;
use sp_sandbox::{ReturnValue, Value}; use sp_sandbox::{ReturnValue, Value};
use crate::wasm::tests::MockExt; use crate::{
use crate::wasm::Runtime; wasm::{Runtime, runtime::TrapReason, tests::MockExt},
use crate::exec::Ext; exec::Ext,
use crate::gas::Gas; gas::Gas,
};
struct TestRuntime {
value: u32,
}
impl TestRuntime {
fn set_trap_reason(&mut self, _reason: TrapReason) {}
}
#[test] #[test]
fn macro_unmarshall_then_body_then_marshall_value_or_trap() { fn macro_unmarshall_then_body_then_marshall_value_or_trap() {
fn test_value( fn test_value(
_ctx: &mut u32, _ctx: &mut TestRuntime,
args: &[sp_sandbox::Value], args: &[sp_sandbox::Value],
) -> Result<ReturnValue, sp_sandbox::HostError> { ) -> Result<ReturnValue, sp_sandbox::HostError> {
let mut args = args.iter(); let mut args = args.iter();
@@ -224,7 +239,7 @@ mod tests {
_ctx, _ctx,
(a: u32, b: u32) -> u32 => { (a: u32, b: u32) -> u32 => {
if b == 0 { if b == 0 {
Err(sp_sandbox::HostError) Err(crate::wasm::runtime::TrapReason::Termination)
} else { } else {
Ok(a / b) Ok(a / b)
} }
@@ -232,7 +247,7 @@ mod tests {
) )
} }
let ctx = &mut 0; let ctx = &mut TestRuntime { value: 0 };
assert_eq!( assert_eq!(
test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(), test_value(ctx, &[Value::I32(15), Value::I32(3)]).unwrap(),
ReturnValue::Value(Value::I32(5)), ReturnValue::Value(Value::I32(5)),
@@ -243,7 +258,7 @@ mod tests {
#[test] #[test]
fn macro_unmarshall_then_body_then_marshall_unit() { fn macro_unmarshall_then_body_then_marshall_unit() {
fn test_unit( fn test_unit(
ctx: &mut u32, ctx: &mut TestRuntime,
args: &[sp_sandbox::Value], args: &[sp_sandbox::Value],
) -> Result<ReturnValue, sp_sandbox::HostError> { ) -> Result<ReturnValue, sp_sandbox::HostError> {
let mut args = args.iter(); let mut args = args.iter();
@@ -251,16 +266,16 @@ mod tests {
args, args,
ctx, ctx,
(a: u32, b: u32) => { (a: u32, b: u32) => {
*ctx = a + b; ctx.value = a + b;
Ok(()) Ok(())
} }
) )
} }
let ctx = &mut 0; let ctx = &mut TestRuntime { value: 0 };
let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap(); let result = test_unit(ctx, &[Value::I32(2), Value::I32(3)]).unwrap();
assert_eq!(result, ReturnValue::Unit); assert_eq!(result, ReturnValue::Unit);
assert_eq!(*ctx, 5); assert_eq!(ctx.value, 5);
} }
#[test] #[test]
@@ -270,7 +285,7 @@ mod tests {
if !amount.is_zero() { if !amount.is_zero() {
Ok(()) Ok(())
} else { } else {
Err(sp_sandbox::HostError) Err(TrapReason::Termination)
} }
}); });
let _f: fn(&mut Runtime<MockExt>, &[sp_sandbox::Value]) let _f: fn(&mut Runtime<MockExt>, &[sp_sandbox::Value])
@@ -322,7 +337,7 @@ mod tests {
if !amount.is_zero() { if !amount.is_zero() {
Ok(()) Ok(())
} else { } else {
Err(sp_sandbox::HostError) Err(crate::wasm::runtime::TrapReason::Termination)
} }
}, },
); );
+2 -2
View File
@@ -1537,7 +1537,7 @@ mod tests {
&mut gas_meter &mut gas_meter
), ),
Err(ExecError { Err(ExecError {
error: Error::<Test>::ContractTrapped.into(), error: Error::<Test>::TooManyTopics.into(),
origin: ErrorOrigin::Caller, origin: ErrorOrigin::Caller,
}) })
); );
@@ -1582,7 +1582,7 @@ mod tests {
&mut gas_meter &mut gas_meter
), ),
Err(ExecError { Err(ExecError {
error: Error::<Test>::ContractTrapped.into(), error: Error::<Test>::DuplicateTopics.into(),
origin: ErrorOrigin::Caller, origin: ErrorOrigin::Caller,
}) })
); );
@@ -491,9 +491,14 @@ mod tests {
} }
} }
/// Using unreachable statements triggers unreachable warnings in the generated code
#[allow(unreachable_code)]
mod env {
use super::*;
// Define test environment for tests. We need ImportSatisfyCheck // Define test environment for tests. We need ImportSatisfyCheck
// implementation from it. So actual implementations doesn't matter. // implementation from it. So actual implementations doesn't matter.
define_env!(TestEnv, <E: Ext>, define_env!(Test, <E: Ext>,
panic(_ctx) => { unreachable!(); }, panic(_ctx) => { unreachable!(); },
// gas is an implementation defined function and a contract can't import it. // gas is an implementation defined function and a contract can't import it.
@@ -503,6 +508,7 @@ mod tests {
seal_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); }, seal_println(_ctx, _ptr: u32, _len: u32) => { unreachable!(); },
); );
}
macro_rules! prepare_test { macro_rules! prepare_test {
($name:ident, $wat:expr, $($expected:tt)*) => { ($name:ident, $wat:expr, $($expected:tt)*) => {
@@ -520,7 +526,7 @@ mod tests {
}, },
.. Default::default() .. Default::default()
}; };
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule); let r = prepare_contract::<env::Test, crate::tests::Test>(wasm.as_ref(), &schedule);
assert_matches!(r, $($expected)*); assert_matches!(r, $($expected)*);
} }
}; };
@@ -931,7 +937,7 @@ mod tests {
).unwrap(); ).unwrap();
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
schedule.enable_println = true; schedule.enable_println = true;
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule); let r = prepare_contract::<env::Test, crate::tests::Test>(wasm.as_ref(), &schedule);
assert_matches!(r, Ok(_)); assert_matches!(r, Ok(_));
} }
} }
+88 -127
View File
@@ -90,7 +90,7 @@ impl From<ExecReturnValue> for ReturnCode {
} }
/// The data passed through when a contract uses `seal_return`. /// The data passed through when a contract uses `seal_return`.
struct ReturnData { pub struct ReturnData {
/// The flags as passed through by the contract. They are still unchecked and /// The flags as passed through by the contract. They are still unchecked and
/// will later be parsed into a `ReturnFlags` bitflags struct. /// will later be parsed into a `ReturnFlags` bitflags struct.
flags: u32, flags: u32,
@@ -104,7 +104,7 @@ struct ReturnData {
/// occurred (the SupervisorError variant). /// occurred (the SupervisorError variant).
/// The other case is where the trap does not constitute an error but rather was invoked /// The other case is where the trap does not constitute an error but rather was invoked
/// as a quick way to terminate the application (all other variants). /// as a quick way to terminate the application (all other variants).
enum TrapReason { pub enum TrapReason {
/// The supervisor trapped the contract because of an error condition occurred during /// The supervisor trapped the contract because of an error condition occurred during
/// execution in privileged code. /// execution in privileged code.
SupervisorError(DispatchError), SupervisorError(DispatchError),
@@ -117,9 +117,15 @@ enum TrapReason {
Restoration, Restoration,
} }
impl<T: Into<DispatchError>> From<T> for TrapReason {
fn from(from: T) -> Self {
Self::SupervisorError(from.into())
}
}
#[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[cfg_attr(test, derive(Debug, PartialEq, Eq))]
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub enum RuntimeToken { enum RuntimeToken {
/// Charge the gas meter with the cost of a metering block. The charged costs are /// 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. /// the supplied cost of the block plus the overhead of the metering itself.
MeteringBlock(u32), MeteringBlock(u32),
@@ -369,21 +375,25 @@ where
} }
} }
/// 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
/// the host functions defined through the said macro. It should **not** be called
/// manually.
pub fn set_trap_reason(&mut self, reason: TrapReason) {
self.trap_reason = Some(reason);
}
/// Charge the gas meter with the specified token. /// Charge the gas meter with the specified token.
/// ///
/// Returns `Err(HostError)` if there is not enough gas. /// Returns `Err(HostError)` if there is not enough gas.
fn charge_gas<Tok>(&mut self, token: Tok) -> Result<(), sp_sandbox::HostError> fn charge_gas<Tok>(&mut self, token: Tok) -> Result<(), DispatchError>
where where
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>, Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
{ {
match self.gas_meter.charge(&self.schedule.host_fn_weights, token) { match self.gas_meter.charge(&self.schedule.host_fn_weights, token) {
GasMeterResult::Proceed => Ok(()), GasMeterResult::Proceed => Ok(()),
GasMeterResult::OutOfGas => { GasMeterResult::OutOfGas => Err(Error::<E::T>::OutOfGas.into())
self.trap_reason = Some(
TrapReason::SupervisorError(Error::<E::T>::OutOfGas.into())
);
Err(sp_sandbox::HostError)
},
} }
} }
@@ -392,12 +402,12 @@ where
/// Returns `Err` if one of the following conditions occurs: /// Returns `Err` if one of the following conditions occurs:
/// ///
/// - requested buffer is not within the bounds of the sandbox memory. /// - requested buffer is not within the bounds of the sandbox memory.
fn read_sandbox_memory(&mut self, ptr: u32, len: u32) fn read_sandbox_memory(&self, ptr: u32, len: u32)
-> Result<Vec<u8>, sp_sandbox::HostError> -> Result<Vec<u8>, DispatchError>
{ {
let mut buf = vec![0u8; len as usize]; let mut buf = vec![0u8; len as usize];
self.memory.get(ptr, buf.as_mut_slice()) self.memory.get(ptr, buf.as_mut_slice())
.map_err(|_| self.store_err(Error::<E::T>::OutOfBounds))?; .map_err(|_| Error::<E::T>::OutOfBounds)?;
Ok(buf) Ok(buf)
} }
@@ -406,10 +416,10 @@ where
/// Returns `Err` if one of the following conditions occurs: /// Returns `Err` if one of the following conditions occurs:
/// ///
/// - requested buffer is not within the bounds of the sandbox memory. /// - requested buffer is not within the bounds of the sandbox memory.
fn read_sandbox_memory_into_buf(&mut self, ptr: u32, buf: &mut [u8]) fn read_sandbox_memory_into_buf(&self, ptr: u32, buf: &mut [u8])
-> Result<(), sp_sandbox::HostError> -> Result<(), DispatchError>
{ {
self.memory.get(ptr, buf).map_err(|_| self.store_err(Error::<E::T>::OutOfBounds)) self.memory.get(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
} }
/// Read designated chunk from the sandbox memory and attempt to decode into the specified type. /// Read designated chunk from the sandbox memory and attempt to decode into the specified type.
@@ -418,11 +428,11 @@ where
/// ///
/// - requested buffer is not within the bounds of the sandbox memory. /// - requested buffer is not within the bounds of the sandbox memory.
/// - the buffer contents cannot be decoded as the required type. /// - the buffer contents cannot be decoded as the required type.
fn read_sandbox_memory_as<D: Decode>(&mut self, ptr: u32, len: u32) fn read_sandbox_memory_as<D: Decode>(&self, ptr: u32, len: u32)
-> Result<D, sp_sandbox::HostError> -> Result<D, DispatchError>
{ {
let buf = self.read_sandbox_memory(ptr, len)?; let buf = self.read_sandbox_memory(ptr, len)?;
D::decode(&mut &buf[..]).map_err(|_| self.store_err(Error::<E::T>::DecodingFailed)) D::decode(&mut &buf[..]).map_err(|_| Error::<E::T>::DecodingFailed.into())
} }
/// Write the given buffer to the designated location in the sandbox memory. /// Write the given buffer to the designated location in the sandbox memory.
@@ -430,8 +440,8 @@ where
/// Returns `Err` if one of the following conditions occurs: /// Returns `Err` if one of the following conditions occurs:
/// ///
/// - designated area is not within the bounds of the sandbox memory. /// - designated area is not within the bounds of the sandbox memory.
fn write_sandbox_memory(&mut self, ptr: u32, buf: &[u8]) -> Result<(), sp_sandbox::HostError> { fn write_sandbox_memory(&mut self, ptr: u32, buf: &[u8]) -> Result<(), DispatchError> {
self.memory.set(ptr, buf).map_err(|_| self.store_err(Error::<E::T>::OutOfBounds)) self.memory.set(ptr, buf).map_err(|_| Error::<E::T>::OutOfBounds.into())
} }
/// Write the given buffer and its length to the designated locations in sandbox memory and /// Write the given buffer and its length to the designated locations in sandbox memory and
@@ -460,7 +470,7 @@ where
buf: &[u8], buf: &[u8],
allow_skip: bool, allow_skip: bool,
create_token: impl FnOnce(u32) -> Option<RuntimeToken>, create_token: impl FnOnce(u32) -> Option<RuntimeToken>,
) -> Result<(), sp_sandbox::HostError> ) -> Result<(), DispatchError>
{ {
if allow_skip && out_ptr == u32::max_value() { if allow_skip && out_ptr == u32::max_value() {
return Ok(()); return Ok(());
@@ -470,7 +480,7 @@ where
let len: u32 = self.read_sandbox_memory_as(out_len_ptr, 4)?; let len: u32 = self.read_sandbox_memory_as(out_len_ptr, 4)?;
if len < buf_len { if len < buf_len {
Err(self.store_err(Error::<E::T>::OutputBufferTooSmall))? Err(Error::<E::T>::OutputBufferTooSmall)?
} }
if let Some(token) = create_token(buf_len) { if let Some(token) = create_token(buf_len) {
@@ -480,7 +490,7 @@ where
self.memory.set(out_ptr, buf).and_then(|_| { self.memory.set(out_ptr, buf).and_then(|_| {
self.memory.set(out_len_ptr, &buf_len.encode()) self.memory.set(out_len_ptr, &buf_len.encode())
}) })
.map_err(|_| self.store_err(Error::<E::T>::OutOfBounds))?; .map_err(|_| Error::<E::T>::OutOfBounds)?;
Ok(()) Ok(())
} }
@@ -503,7 +513,7 @@ where
input_ptr: u32, input_ptr: u32,
input_len: u32, input_len: u32,
output_ptr: u32, output_ptr: u32,
) -> Result<(), sp_sandbox::HostError> ) -> Result<(), DispatchError>
where where
F: FnOnce(&[u8]) -> R, F: FnOnce(&[u8]) -> R,
R: AsRef<[u8]>, R: AsRef<[u8]>,
@@ -517,48 +527,6 @@ where
Ok(()) Ok(())
} }
/// Stores a DispatchError returned from an Ext function into the trap_reason.
///
/// This allows through supervisor generated errors to the caller.
fn store_err<Error>(&mut self, err: Error) -> sp_sandbox::HostError
where
Error: Into<DispatchError>,
{
self.trap_reason = Some(TrapReason::SupervisorError(err.into()));
sp_sandbox::HostError
}
/// Used by Runtime API that calls into other contracts.
///
/// Those need to transform the the `ExecResult` returned from the execution into
/// a `ReturnCode`. If this conversion fails because the `ExecResult` constitutes a
/// a fatal error then this error is stored in the `ExecutionContext` so it can be
/// extracted for display in the UI.
fn map_exec_result(&mut self, result: ExecResult) -> Result<ReturnCode, sp_sandbox::HostError> {
match Self::exec_into_return_code(result) {
Ok(code) => Ok(code),
Err(err) => Err(self.store_err(err)),
}
}
/// Try to convert an error into a `ReturnCode`.
///
/// Used to decide between fatal and non-fatal errors.
fn map_dispatch_result<T>(&mut self, result: Result<T, DispatchError>)
-> Result<ReturnCode, sp_sandbox::HostError>
{
let err = if let Err(err) = result {
err
} else {
return Ok(ReturnCode::Success)
};
match Self::err_into_return_code(err) {
Ok(code) => Ok(code),
Err(err) => Err(self.store_err(err)),
}
}
/// Fallible conversion of `DispatchError` to `ReturnCode`. /// Fallible conversion of `DispatchError` to `ReturnCode`.
fn err_into_return_code(from: DispatchError) -> Result<ReturnCode, DispatchError> { fn err_into_return_code(from: DispatchError) -> Result<ReturnCode, DispatchError> {
use ReturnCode::*; use ReturnCode::*;
@@ -638,7 +606,7 @@ define_env!(Env, <E: Ext>,
seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => { seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
ctx.charge_gas(RuntimeToken::SetStorage(value_len))?; ctx.charge_gas(RuntimeToken::SetStorage(value_len))?;
if value_len > ctx.ext.max_value_size() { if value_len > ctx.ext.max_value_size() {
Err(ctx.store_err(Error::<E::T>::ValueTooLarge))?; Err(Error::<E::T>::ValueTooLarge)?;
} }
let mut key: StorageKey = [0; 32]; let mut key: StorageKey = [0; 32];
ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?;
@@ -715,7 +683,13 @@ define_env!(Env, <E: Ext>,
ctx.read_sandbox_memory_as(value_ptr, value_len)?; ctx.read_sandbox_memory_as(value_ptr, value_len)?;
let result = ctx.ext.transfer(&callee, value); let result = ctx.ext.transfer(&callee, value);
ctx.map_dispatch_result(result) match result {
Ok(()) => Ok(ReturnCode::Success),
Err(err) => {
let code = Runtime::<E>::err_into_return_code(err)?;
Ok(code)
}
}
}, },
// Make a call to another contract. // Make a call to another contract.
@@ -797,7 +771,7 @@ define_env!(Env, <E: Ext>,
Some(RuntimeToken::CallCopyOut(len)) Some(RuntimeToken::CallCopyOut(len))
})?; })?;
} }
ctx.map_exec_result(call_outcome) Ok(Runtime::<E>::exec_into_return_code(call_outcome)?)
}, },
// Instantiate a contract with the specified code hash. // Instantiate a contract with the specified code hash.
@@ -899,7 +873,7 @@ define_env!(Env, <E: Ext>,
Some(RuntimeToken::InstantiateCopyOut(len)) Some(RuntimeToken::InstantiateCopyOut(len))
})?; })?;
} }
ctx.map_exec_result(instantiate_outcome.map(|(_id, retval)| retval)) Ok(Runtime::<E>::exec_into_return_code(instantiate_outcome.map(|(_id, retval)| retval))?)
}, },
// Remove the calling account and transfer remaining balance. // Remove the calling account and transfer remaining balance.
@@ -925,10 +899,8 @@ define_env!(Env, <E: Ext>,
let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId = let beneficiary: <<E as Ext>::T as frame_system::Config>::AccountId =
ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?; ctx.read_sandbox_memory_as(beneficiary_ptr, beneficiary_len)?;
if let Ok(_) = ctx.ext.terminate(&beneficiary).map_err(|e| ctx.store_err(e)) { ctx.ext.terminate(&beneficiary)?;
ctx.trap_reason = Some(TrapReason::Termination); Err(TrapReason::Termination)
}
Err(sp_sandbox::HostError)
}, },
seal_input(ctx, buf_ptr: u32, buf_len_ptr: u32) => { seal_input(ctx, buf_ptr: u32, buf_len_ptr: u32) => {
@@ -936,9 +908,10 @@ define_env!(Env, <E: Ext>,
if let Some(input) = ctx.input_data.take() { if let Some(input) = ctx.input_data.take() {
ctx.write_sandbox_output(buf_ptr, buf_len_ptr, &input, false, |len| { ctx.write_sandbox_output(buf_ptr, buf_len_ptr, &input, false, |len| {
Some(RuntimeToken::InputCopyOut(len)) Some(RuntimeToken::InputCopyOut(len))
}) })?;
Ok(())
} else { } else {
Err(sp_sandbox::HostError) Err(Error::<E::T>::InputAlreadyRead.into())
} }
}, },
@@ -961,15 +934,10 @@ define_env!(Env, <E: Ext>,
// Using a reserved bit triggers a trap. // Using a reserved bit triggers a trap.
seal_return(ctx, flags: u32, data_ptr: u32, data_len: u32) => { seal_return(ctx, flags: u32, data_ptr: u32, data_len: u32) => {
ctx.charge_gas(RuntimeToken::Return(data_len))?; ctx.charge_gas(RuntimeToken::Return(data_len))?;
ctx.trap_reason = Some(TrapReason::Return(ReturnData { Err(TrapReason::Return(ReturnData {
flags, flags,
data: ctx.read_sandbox_memory(data_ptr, data_len)?, data: ctx.read_sandbox_memory(data_ptr, data_len)?,
})); }))
// The trap mechanism is used to immediately terminate the execution.
// This trap should be handled appropriately before returning the result
// to the user of this crate.
Err(sp_sandbox::HostError)
}, },
// Stores the address of the caller into the supplied buffer. // Stores the address of the caller into the supplied buffer.
@@ -984,9 +952,9 @@ define_env!(Env, <E: Ext>,
// address of the contract will be returned. The value is encoded as T::AccountId. // address of the contract will be returned. The value is encoded as T::AccountId.
seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Caller)?; ctx.charge_gas(RuntimeToken::Caller)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged
) )?)
}, },
// Stores the address of the current contract into the supplied buffer. // Stores the address of the current contract into the supplied buffer.
@@ -997,9 +965,9 @@ define_env!(Env, <E: Ext>,
// space at `out_ptr` is less than the size of the value a trap is triggered. // space at `out_ptr` is less than the size of the value a trap is triggered.
seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Address)?; ctx.charge_gas(RuntimeToken::Address)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged
) )?)
}, },
// Stores the price for the specified amount of gas into the supplied buffer. // Stores the price for the specified amount of gas into the supplied buffer.
@@ -1017,9 +985,9 @@ define_env!(Env, <E: Ext>,
// gas can be smaller than one. // gas can be smaller than one.
seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => { seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::WeightToFee)?; ctx.charge_gas(RuntimeToken::WeightToFee)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false, already_charged
) )?)
}, },
// Stores the amount of gas left into the supplied buffer. // Stores the amount of gas left into the supplied buffer.
@@ -1032,9 +1000,9 @@ define_env!(Env, <E: Ext>,
// The data is encoded as Gas. // The data is encoded as Gas.
seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::GasLeft)?; ctx.charge_gas(RuntimeToken::GasLeft)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, already_charged
) )?)
}, },
// Stores the balance of the current account into the supplied buffer. // Stores the balance of the current account into the supplied buffer.
@@ -1047,9 +1015,9 @@ define_env!(Env, <E: Ext>,
// The data is encoded as T::Balance. // The data is encoded as T::Balance.
seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Balance)?; ctx.charge_gas(RuntimeToken::Balance)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged
) )?)
}, },
// Stores the value transferred along with this call or as endowment into the supplied buffer. // Stores the value transferred along with this call or as endowment into the supplied buffer.
@@ -1062,9 +1030,9 @@ define_env!(Env, <E: Ext>,
// The data is encoded as T::Balance. // The data is encoded as T::Balance.
seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::ValueTransferred)?; ctx.charge_gas(RuntimeToken::ValueTransferred)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false, already_charged
) )?)
}, },
// Stores a random number for the current block and the given subject into the supplied buffer. // Stores a random number for the current block and the given subject into the supplied buffer.
@@ -1078,12 +1046,12 @@ define_env!(Env, <E: Ext>,
seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => { seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Random)?; ctx.charge_gas(RuntimeToken::Random)?;
if subject_len > ctx.schedule.limits.subject_len { if subject_len > ctx.schedule.limits.subject_len {
return Err(sp_sandbox::HostError); Err(Error::<E::T>::RandomSubjectTooLong)?;
} }
let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false, already_charged
) )?)
}, },
// Load the latest block timestamp into the supplied buffer // Load the latest block timestamp into the supplied buffer
@@ -1094,9 +1062,9 @@ define_env!(Env, <E: Ext>,
// space at `out_ptr` is less than the size of the value a trap is triggered. // space at `out_ptr` is less than the size of the value a trap is triggered.
seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::Now)?; ctx.charge_gas(RuntimeToken::Now)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged
) )?)
}, },
// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer. // Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer.
@@ -1104,9 +1072,9 @@ define_env!(Env, <E: Ext>,
// The data is encoded as T::Balance. // The data is encoded as T::Balance.
seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::MinimumBalance)?; ctx.charge_gas(RuntimeToken::MinimumBalance)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged
) )?)
}, },
// Stores the tombstone deposit into the supplied buffer. // Stores the tombstone deposit into the supplied buffer.
@@ -1126,9 +1094,9 @@ define_env!(Env, <E: Ext>,
// is commonly referred as subsistence threshold in code. // is commonly referred as subsistence threshold in code.
seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::TombstoneDeposit)?; ctx.charge_gas(RuntimeToken::TombstoneDeposit)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false, already_charged
) )?)
}, },
// Try to restore the given destination contract sacrificing the caller. // Try to restore the given destination contract sacrificing the caller.
@@ -1189,21 +1157,14 @@ define_env!(Env, <E: Ext>,
delta.push(delta_key); delta.push(delta_key);
// Offset key_ptr to the next element. // Offset key_ptr to the next element.
key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or_else(|| sp_sandbox::HostError)?; key_ptr = key_ptr.checked_add(KEY_SIZE as u32).ok_or(Error::<E::T>::OutOfBounds)?;
} }
delta delta
}; };
if let Ok(()) = ctx.ext.restore_to( ctx.ext.restore_to(dest, code_hash, rent_allowance, delta)?;
dest, Err(TrapReason::Restoration)
code_hash,
rent_allowance,
delta,
).map_err(|e| ctx.store_err(e)) {
ctx.trap_reason = Some(TrapReason::Restoration);
}
Err(sp_sandbox::HostError)
}, },
// Deposit a contract event with the data buffer and optional list of topics. There is a limit // Deposit a contract event with the data buffer and optional list of topics. There is a limit
@@ -1217,13 +1178,13 @@ define_env!(Env, <E: Ext>,
seal_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => { seal_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => {
let num_topic = topics_len let num_topic = topics_len
.checked_div(sp_std::mem::size_of::<TopicOf<E::T>>() as u32) .checked_div(sp_std::mem::size_of::<TopicOf<E::T>>() as u32)
.ok_or_else(|| ctx.store_err("Zero sized topics are not allowed"))?; .ok_or_else(|| "Zero sized topics are not allowed")?;
ctx.charge_gas(RuntimeToken::DepositEvent { ctx.charge_gas(RuntimeToken::DepositEvent {
num_topic, num_topic,
len: data_len, len: data_len,
})?; })?;
if data_len > ctx.ext.max_value_size() { if data_len > ctx.ext.max_value_size() {
Err(ctx.store_err(Error::<E::T>::ValueTooLarge))?; Err(Error::<E::T>::ValueTooLarge)?;
} }
let mut topics: Vec::<TopicOf<<E as Ext>::T>> = match topics_len { let mut topics: Vec::<TopicOf<<E as Ext>::T>> = match topics_len {
@@ -1233,12 +1194,12 @@ define_env!(Env, <E: Ext>,
// If there are more than `event_topics`, then trap. // If there are more than `event_topics`, then trap.
if topics.len() > ctx.schedule.limits.event_topics as usize { if topics.len() > ctx.schedule.limits.event_topics as usize {
return Err(sp_sandbox::HostError); Err(Error::<E::T>::TooManyTopics)?;
} }
// Check for duplicate topics. If there are any, then trap. // Check for duplicate topics. If there are any, then trap.
if has_duplicates(&mut topics) { if has_duplicates(&mut topics) {
return Err(sp_sandbox::HostError); Err(Error::<E::T>::DuplicateTopics)?;
} }
let event_data = ctx.read_sandbox_memory(data_ptr, data_len)?; let event_data = ctx.read_sandbox_memory(data_ptr, data_len)?;
@@ -1272,9 +1233,9 @@ define_env!(Env, <E: Ext>,
// The data is encoded as T::Balance. // The data is encoded as T::Balance.
seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::RentAllowance)?; ctx.charge_gas(RuntimeToken::RentAllowance)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, already_charged
) )?)
}, },
// Prints utf8 encoded string from the data buffer. // Prints utf8 encoded string from the data buffer.
@@ -1296,9 +1257,9 @@ define_env!(Env, <E: Ext>,
// space at `out_ptr` is less than the size of the value a trap is triggered. // space at `out_ptr` is less than the size of the value a trap is triggered.
seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => { seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeToken::BlockNumber)?; ctx.charge_gas(RuntimeToken::BlockNumber)?;
ctx.write_sandbox_output( Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged
) )?)
}, },
// Computes the SHA2 256-bit hash on the given input buffer. // Computes the SHA2 256-bit hash on the given input buffer.
@@ -1323,7 +1284,7 @@ define_env!(Env, <E: Ext>,
// directly into this buffer. // directly into this buffer.
seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashSha256(input_len))?; ctx.charge_gas(RuntimeToken::HashSha256(input_len))?;
ctx.compute_hash_on_intermediate_buffer(sha2_256, input_ptr, input_len, output_ptr) Ok(ctx.compute_hash_on_intermediate_buffer(sha2_256, input_ptr, input_len, output_ptr)?)
}, },
// Computes the KECCAK 256-bit hash on the given input buffer. // Computes the KECCAK 256-bit hash on the given input buffer.
@@ -1348,7 +1309,7 @@ define_env!(Env, <E: Ext>,
// directly into this buffer. // directly into this buffer.
seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashKeccak256(input_len))?; ctx.charge_gas(RuntimeToken::HashKeccak256(input_len))?;
ctx.compute_hash_on_intermediate_buffer(keccak_256, input_ptr, input_len, output_ptr) Ok(ctx.compute_hash_on_intermediate_buffer(keccak_256, input_ptr, input_len, output_ptr)?)
}, },
// Computes the BLAKE2 256-bit hash on the given input buffer. // Computes the BLAKE2 256-bit hash on the given input buffer.
@@ -1373,7 +1334,7 @@ define_env!(Env, <E: Ext>,
// directly into this buffer. // directly into this buffer.
seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashBlake256(input_len))?; ctx.charge_gas(RuntimeToken::HashBlake256(input_len))?;
ctx.compute_hash_on_intermediate_buffer(blake2_256, input_ptr, input_len, output_ptr) Ok(ctx.compute_hash_on_intermediate_buffer(blake2_256, input_ptr, input_len, output_ptr)?)
}, },
// Computes the BLAKE2 128-bit hash on the given input buffer. // Computes the BLAKE2 128-bit hash on the given input buffer.
@@ -1398,6 +1359,6 @@ define_env!(Env, <E: Ext>,
// directly into this buffer. // directly into this buffer.
seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => { seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
ctx.charge_gas(RuntimeToken::HashBlake128(input_len))?; ctx.charge_gas(RuntimeToken::HashBlake128(input_len))?;
ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr) Ok(ctx.compute_hash_on_intermediate_buffer(blake2_128, input_ptr, input_len, output_ptr)?)
}, },
); );