contracts: Add seal_rent_status (#8780)

* Move public functions up in rent.rs

* Added RentStatus

* Fix test name for consistency

Co-authored-by: Michael Müller <michi@parity.io>

* Mark rent functions as unstable

* Add unstable interfaces to README

* Fix doc typos

Co-authored-by: Andrew Jones <ascjones@gmail.com>

* Use DefaultNoBound

* Simplify calc_share(1)

* Don't output empty debug messages

* Make `seal_debug_message` unstable

Co-authored-by: Michael Müller <michi@parity.io>
Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
Alexander Theißen
2021-05-20 14:01:43 +02:00
committed by GitHub
parent f29a6fdad3
commit 0057c0b53f
17 changed files with 1380 additions and 1109 deletions
@@ -50,7 +50,12 @@ macro_rules! gen_signature_dispatch {
$name:ident
( $ctx:ident $( , $names:ident : $params:ty )* ) $( -> $returns:ty )* , $($rest:tt)*
) => {
if stringify!($module).as_bytes() == $needle_module && stringify!($name).as_bytes() == $needle_name {
let module = stringify!($module).as_bytes();
#[cfg(not(feature = "unstable-interface"))]
if module == b"__unstable__" {
return false;
}
if module == $needle_module && stringify!($name).as_bytes() == $needle_name {
let signature = gen_signature!( ( $( $params ),* ) $( -> $returns )* );
if $needle_sig == &signature {
return true;
@@ -127,8 +132,8 @@ macro_rules! unmarshall_then_body_then_marshall {
}
macro_rules! define_func {
( < E: $seal_ty:tt > $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
fn $name< E: $seal_ty >(
( $trait:tt $name:ident ( $ctx: ident $(, $names:ident : $params:ty)*) $(-> $returns:ty)* => $body:tt ) => {
fn $name< E: $trait >(
$ctx: &mut $crate::wasm::Runtime<E>,
args: &[sp_sandbox::Value],
) -> Result<sp_sandbox::ReturnValue, sp_sandbox::HostError>
@@ -149,24 +154,52 @@ macro_rules! define_func {
};
}
macro_rules! register_func {
( $reg_cb:ident, < E: $seal_ty:tt > ; ) => {};
( $reg_cb:ident, < E: $seal_ty:tt > ;
macro_rules! register_body {
( $reg_cb:ident, $trait:tt;
$module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
$( -> $returns:ty )* => $body:tt $($rest:tt)*
$( -> $returns:ty )* => $body:tt
) => {
$reg_cb(
stringify!($module).as_bytes(),
stringify!($name).as_bytes(),
{
define_func!(
< E: $seal_ty > $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
$trait $name ( $ctx $(, $names : $params )* ) $( -> $returns )* => $body
);
$name::<E>
}
);
register_func!( $reg_cb, < E: $seal_ty > ; $($rest)* );
}
}
macro_rules! register_func {
( $reg_cb:ident, $trait:tt; ) => {};
( $reg_cb:ident, $trait:tt;
__unstable__ $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
$( -> $returns:ty )* => $body:tt $($rest:tt)*
) => {
#[cfg(feature = "unstable-interface")]
register_body!(
$reg_cb, $trait;
__unstable__ $name
( $ctx $( , $names : $params )* )
$( -> $returns )* => $body
);
register_func!( $reg_cb, $trait; $($rest)* );
};
( $reg_cb:ident, $trait:tt;
$module:ident $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
$( -> $returns:ty )* => $body:tt $($rest:tt)*
) => {
register_body!(
$reg_cb, $trait;
$module $name
( $ctx $( , $names : $params )* )
$( -> $returns )* => $body
);
register_func!( $reg_cb, $trait; $($rest)* );
};
}
@@ -178,7 +211,7 @@ macro_rules! register_func {
/// It's up to the user of this macro to check signatures of wasm code to be executed
/// and reject the code if any imported function has a mismatched signature.
macro_rules! define_env {
( $init_name:ident , < E: $seal_ty:tt > ,
( $init_name:ident , < E: $trait:tt > ,
$( [$module:ident] $name:ident ( $ctx:ident $( , $names:ident : $params:ty )* )
$( -> $returns:ty )* => $body:tt , )*
) => {
@@ -204,7 +237,7 @@ macro_rules! define_env {
fn impls<F: FnMut(&[u8], &[u8], $crate::wasm::env_def::HostFunc<E>)>(f: &mut F) {
register_func!(
f,
< E: $seal_ty > ;
$trait;
$( $module $name ( $ctx $( , $names : $params )* ) $( -> $returns)* => $body )*
);
}
@@ -285,7 +318,7 @@ mod tests {
#[test]
fn macro_define_func() {
define_func!( <E: Ext> seal_gas (_ctx, amount: u32) => {
define_func!( Ext seal_gas (_ctx, amount: u32) => {
let amount = Weight::from(amount);
if !amount.is_zero() {
Ok(())
+90 -40
View File
@@ -247,6 +247,7 @@ mod tests {
RentParams, ExecError, ErrorOrigin,
},
gas::GasMeter,
rent::RentStatus,
tests::{Test, Call, ALICE, BOB},
};
use std::collections::HashMap;
@@ -452,6 +453,9 @@ mod tests {
fn rent_params(&self) -> &RentParams<Self::T> {
&self.rent_params
}
fn rent_status(&mut self, _at_refcount: u32) -> RentStatus<Self::T> {
Default::default()
}
fn gas_meter(&mut self) -> &mut GasMeter<Self::T> {
&mut self.gas_meter
}
@@ -1817,9 +1821,14 @@ mod tests {
);
}
const CODE_RENT_PARAMS: &str = r#"
#[test]
#[cfg(feature = "unstable-interface")]
fn rent_params_work() {
const CODE_RENT_PARAMS: &str = r#"
(module
(import "seal0" "seal_rent_params" (func $seal_rent_params (param i32 i32)))
(import "__unstable__" "seal_rent_params" (func $seal_rent_params (param i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
@@ -1846,9 +1855,6 @@ mod tests {
(func (export "deploy"))
)
"#;
#[test]
fn rent_params_work() {
let output = execute(
CODE_RENT_PARAMS,
vec![],
@@ -1858,9 +1864,56 @@ mod tests {
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_params });
}
const CODE_DEBUG_MESSAGE: &str = r#"
#[test]
#[cfg(feature = "unstable-interface")]
fn rent_status_works() {
const CODE_RENT_STATUS: &str = r#"
(module
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
(import "__unstable__" "seal_rent_status" (func $seal_rent_status (param i32 i32 i32)))
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
(import "env" "memory" (memory 1 1))
;; [0, 4) buffer size = 128 bytes
(data (i32.const 0) "\80")
;; [4; inf) buffer where the result is copied
(func (export "call")
;; Load the rent params into memory
(call $seal_rent_status
(i32.const 1) ;; at_refcount
(i32.const 4) ;; Pointer to the output buffer
(i32.const 0) ;; Pointer to the size of the buffer
)
;; Return the contents of the buffer
(call $seal_return
(i32.const 0) ;; return flags
(i32.const 4) ;; buffer pointer
(i32.load (i32.const 0)) ;; buffer size
)
)
(func (export "deploy"))
)
"#;
let output = execute(
CODE_RENT_STATUS,
vec![],
MockExt::default(),
).unwrap();
let rent_status = Bytes(<RentStatus<Test>>::default().encode());
assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: rent_status });
}
#[test]
#[cfg(feature = "unstable-interface")]
fn debug_message_works() {
const CODE_DEBUG_MESSAGE: &str = r#"
(module
(import "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(data (i32.const 0) "Hello World!")
@@ -1876,9 +1929,6 @@ mod tests {
(func (export "deploy"))
)
"#;
#[test]
fn debug_message_works() {
let mut ext = MockExt::default();
execute(
CODE_DEBUG_MESSAGE,
@@ -1889,39 +1939,39 @@ mod tests {
assert_eq!(std::str::from_utf8(&ext.debug_buffer).unwrap(), "Hello World!");
}
const CODE_DEBUG_MESSAGE_FAIL: &str = r#"
(module
(import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
#[test]
#[cfg(feature = "unstable-interface")]
fn debug_message_invalid_utf8_fails() {
const CODE_DEBUG_MESSAGE_FAIL: &str = r#"
(module
(import "__unstable__" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32)))
(import "env" "memory" (memory 1 1))
(data (i32.const 0) "\fc")
(data (i32.const 0) "\fc")
(func (export "call")
(call $seal_debug_message
(i32.const 0) ;; Pointer to the text buffer
(i32.const 1) ;; The size of the buffer
)
drop
(func (export "call")
(call $seal_debug_message
(i32.const 0) ;; Pointer to the text buffer
(i32.const 1) ;; The size of the buffer
)
(func (export "deploy"))
drop
)
"#;
#[test]
fn debug_message_invalid_utf8_fails() {
let mut ext = MockExt::default();
let result = execute(
CODE_DEBUG_MESSAGE_FAIL,
vec![],
&mut ext,
);
assert_eq!(
result,
Err(ExecError {
error: Error::<Test>::DebugMessageInvalidUTF8.into(),
origin: ErrorOrigin::Caller,
})
);
}
(func (export "deploy"))
)
"#;
let mut ext = MockExt::default();
let result = execute(
CODE_DEBUG_MESSAGE_FAIL,
vec![],
&mut ext,
);
assert_eq!(
result,
Err(ExecError {
error: Error::<Test>::DebugMessageInvalidUTF8.into(),
origin: ErrorOrigin::Caller,
})
);
}
}
+62 -34
View File
@@ -73,6 +73,7 @@ pub enum ReturnCode {
NotCallable = 8,
/// The call to `seal_debug_message` had no effect because debug message
/// recording was disabled.
#[cfg(feature = "unstable-interface")]
LoggingDisabled = 9,
}
@@ -179,6 +180,7 @@ pub enum RuntimeCosts {
/// Weight of calling `seal_deposit_event` with the given number of topics and event size.
DepositEvent{num_topic: u32, len: u32},
/// Weight of calling `seal_debug_message`.
#[cfg(feature = "unstable-interface")]
DebugMessage,
/// Weight of calling `seal_set_rent_allowance`.
SetRentAllowance,
@@ -220,8 +222,6 @@ pub enum RuntimeCosts {
ChainExtension(u64),
/// Weight charged for copying data from the sandbox.
CopyIn(u32),
/// Weight of calling `seal_rent_params`.
RentParams,
}
impl RuntimeCosts {
@@ -260,6 +260,7 @@ impl RuntimeCosts {
DepositEvent{num_topic, len} => s.deposit_event
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
#[cfg(feature = "unstable-interface")]
DebugMessage => s.debug_message,
SetRentAllowance => s.set_rent_allowance,
SetStorage(len) => s.set_storage
@@ -290,7 +291,6 @@ impl RuntimeCosts {
.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()),
RentParams => s.rent_params,
};
RuntimeToken {
#[cfg(test)]
@@ -1390,35 +1390,6 @@ define_env!(Env, <E: Ext>,
)?)
},
// Emit a custom debug message.
//
// No newlines are added to the supplied message.
// Specifying invalid UTF-8 triggers a trap.
//
// This is a no-op if debug message recording is disabled which is always the case
// when the code is executing on-chain. The message is interpreted as UTF-8 and
// appended to the debug buffer which is then supplied to the calling RPC client.
//
// # Note
//
// Even though no action is taken when debug message recording is disabled there is still
// a non trivial overhead (and weight cost) associated with calling this function. Contract
// languages should remove calls to this function (either at runtime or compile time) when
// not being executed as an RPC. For example, they could allow users to disable logging
// through compile time flags (cargo features) for on-chain deployment. Additionally, the
// return value of this function can be cached in order to prevent further calls at runtime.
[seal0] seal_debug_message(ctx, str_ptr: u32, str_len: u32) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
if ctx.ext.append_debug_buffer("") {
let data = ctx.read_sandbox_memory(str_ptr, str_len)?;
let msg = core::str::from_utf8(&data)
.map_err(|_| <Error<E::T>>::DebugMessageInvalidUTF8)?;
ctx.ext.append_debug_buffer(msg);
return Ok(ReturnCode::Success);
}
Ok(ReturnCode::LoggingDisabled)
},
// Stores the current block number of the current contract into the supplied buffer.
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
@@ -1565,6 +1536,35 @@ define_env!(Env, <E: Ext>,
}
},
// Emit a custom debug message.
//
// No newlines are added to the supplied message.
// Specifying invalid UTF-8 triggers a trap.
//
// This is a no-op if debug message recording is disabled which is always the case
// when the code is executing on-chain. The message is interpreted as UTF-8 and
// appended to the debug buffer which is then supplied to the calling RPC client.
//
// # Note
//
// Even though no action is taken when debug message recording is disabled there is still
// a non trivial overhead (and weight cost) associated with calling this function. Contract
// languages should remove calls to this function (either at runtime or compile time) when
// not being executed as an RPC. For example, they could allow users to disable logging
// through compile time flags (cargo features) for on-chain deployment. Additionally, the
// return value of this function can be cached in order to prevent further calls at runtime.
[__unstable__] seal_debug_message(ctx, str_ptr: u32, str_len: u32) -> ReturnCode => {
ctx.charge_gas(RuntimeCosts::DebugMessage)?;
if ctx.ext.append_debug_buffer("") {
let data = ctx.read_sandbox_memory(str_ptr, str_len)?;
let msg = core::str::from_utf8(&data)
.map_err(|_| <Error<E::T>>::DebugMessageInvalidUTF8)?;
ctx.ext.append_debug_buffer(msg);
return Ok(ReturnCode::Success);
}
Ok(ReturnCode::LoggingDisabled)
},
// Stores the rent params into the supplied buffer.
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
@@ -1579,10 +1579,38 @@ define_env!(Env, <E: Ext>,
// The returned information was collected and cached when the current contract call
// started execution. Any change to those values that happens due to actions of the
// current call or contracts that are called by this contract are not considered.
[seal0] seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
ctx.charge_gas(RuntimeCosts::RentParams)?;
//
// # Unstable
//
// This function is unstable and subject to change (or removal) in the future. Do not
// deploy a contract using it to a production chain.
[__unstable__] seal_rent_params(ctx, out_ptr: u32, out_len_ptr: u32) => {
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &ctx.ext.rent_params().encode(), false, already_charged
)?)
},
// Stores the rent status into the supplied buffer.
//
// The value is stored to linear memory at the address pointed to by `out_ptr`.
// `out_len_ptr` must point to a u32 value that describes the available space at
// `out_ptr`. This call overwrites it with the size of the value. If the available
// space at `out_ptr` is less than the size of the value a trap is triggered.
//
// The data is encoded as [`crate::rent::RentStatus`].
//
// # Parameters
//
// - `at_refcount`: The refcount assumed for the returned `custom_refcount_*` fields
//
// # Unstable
//
// This function is unstable and subject to change (or removal) in the future. Do not
// deploy a contract using it to a production chain.
[__unstable__] seal_rent_status(ctx, at_refcount: u32, out_ptr: u32, out_len_ptr: u32) => {
let rent_status = ctx.ext.rent_status(at_refcount).encode();
Ok(ctx.write_sandbox_output(
out_ptr, out_len_ptr, &rent_status, false, already_charged
)?)
},
);