Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update seal_call to __unstable__ version #960

Merged
merged 13 commits into from
Nov 9, 2021
1 change: 1 addition & 0 deletions .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ multi
postfix
prefilled
recurse
reentrancy
refcount
scalability
scalable
Expand Down
6 changes: 6 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

This is the 6th release candidate for ink! 3.0.

## Added
- Added support for the unstable `seal_call` API ‒ [#960](https://github.com/paritytech/ink/pull/960).
- This API now enables control over the behavior of cross-contract calls, e.g. to forward/clone input,
enable tail calls and control reentrancy.
The crate documentation contains more details on the [`CallFlags`](https://paritytech.github.io/ink/ink_env/struct.CallFlags.html).

## Changed

- Update to `scale-info` 1.0 - [#845](https://github.com/paritytech/ink/pull/845).
Expand Down
121 changes: 121 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,127 @@ impl ReturnFlags {
}
}

/// The flags used to change the behavior of a contract call.
#[derive(Copy, Clone, Debug, Default)]
pub struct CallFlags {
forward_input: bool,
clone_input: bool,
tail_call: bool,
allow_reentry: bool,
}

impl CallFlags {
/// Forwards the input for the current function to the callee.
///
/// # Note
///
/// A forwarding call will consume the current contracts input. Any attempt to
/// access the input after this call returns (e.g. by trying another forwarding call)
/// will lead to a contract revert.
/// Consider using [`Self::set_clone_input`] in order to preserve the input.
pub fn set_forward_input(mut self, forward_input: bool) -> Self {
self.forward_input = forward_input;
self
}

/// Identical to [`Self::set_forward_input`] but without consuming the input.
///
/// This adds some additional weight costs to the call.
///
/// # Note
///
/// This implies [`Self::set_forward_input`] and takes precedence when both are set.
pub fn set_clone_input(mut self, clone_input: bool) -> Self {
self.clone_input = clone_input;
self
}

/// Do not return from the call but rather return the result of the callee to the
/// callers caller.
///
/// # Note
///
/// This makes the current contract completely transparent to its caller by replacing
/// this contracts potential output with the callee ones. Any code after the contract
/// calls has been invoked can be safely considered unreachable.
pub fn set_tail_call(mut self, tail_call: bool) -> Self {
self.tail_call = tail_call;
self
}

/// Allow the callee to reenter into the current contract.
///
/// Without this flag any reentrancy into the current contract that originates from
/// the callee (or any of its callees) is denied. This includes the first callee:
/// You cannot call into yourself with this flag set.
pub fn set_allow_reentry(mut self, allow_reentry: bool) -> Self {
self.allow_reentry = allow_reentry;
self
}

/// Returns the underlying `u32` representation of the call flags.
///
/// This value is used to forward the call flag information to the
/// `contracts` pallet.
pub(crate) fn into_u32(self) -> u32 {
cmichi marked this conversation as resolved.
Show resolved Hide resolved
const FORWARD_INPUT: u32 = 0b0000_0001;
const CLONE_INPUT: u32 = 0b0000_0010;
const TAIL_CALL: u32 = 0b0000_0100;
const ALLOW_REENTRY: u32 = 0b0000_1000;

let mut value: u32 = 0b0000_0000;
if self.forward_input {
value |= FORWARD_INPUT;
}
if self.clone_input {
value |= CLONE_INPUT;
}
if self.tail_call {
value |= TAIL_CALL;
}
if self.allow_reentry {
value |= ALLOW_REENTRY;
}
value
cmichi marked this conversation as resolved.
Show resolved Hide resolved
}

/// Returns `true` if input forwarding is set.
///
/// # Note
///
/// See [`Self::set_forward_input`] for more information.
pub fn forward_input(&self) -> bool {
self.forward_input
}

/// Returns `true` if input cloning is set.
///
/// # Note
///
/// See [`Self::set_clone_input`] for more information.
pub fn clone_input(&self) -> bool {
self.clone_input
}

/// Returns `true` if the tail call property is set.
///
/// # Note
///
/// See [`Self::set_tail_call`] for more information.
pub fn tail_call(&self) -> bool {
self.tail_call
}

/// Returns `true` if call reentry is allowed.
///
/// # Note
///
/// See [`Self::set_allow_reentry`] for more information.
pub fn allow_reentry(&self) -> bool {
self.allow_reentry
}
}

/// Environmental contract functionality that does not require `Environment`.
pub trait EnvBackend {
/// Writes the value to the contract storage under the given key.
Expand Down
95 changes: 92 additions & 3 deletions crates/env/src/call/call_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use crate::{
backend::CallFlags,
call::{
utils::{
EmptyArgumentList,
Expand All @@ -36,14 +37,16 @@ where
{
/// The account ID of the to-be-called smart contract.
callee: E::AccountId,
/// The flags used to change the behavior of a contract call.
call_flags: CallFlags,
/// The maximum gas costs allowed for the call.
gas_limit: u64,
/// The transferred value for the call.
transferred_value: E::Balance,
/// The expected return type.
_return_type: ReturnType<R>,
/// The inputs to the execution which is a selector and encoded arguments.
exec_input: ExecutionInput<Args>,
exec_input: Option<ExecutionInput<Args>>,
cmichi marked this conversation as resolved.
Show resolved Hide resolved
}

impl<E, Args, R> CallParams<E, Args, R>
Expand All @@ -56,6 +59,12 @@ where
&self.callee
}

/// Returns the call flags.
#[inline]
pub(crate) fn call_flags(&self) -> &CallFlags {
&self.call_flags
}

/// Returns the chosen gas limit for the called contract execution.
#[inline]
pub(crate) fn gas_limit(&self) -> u64 {
Expand All @@ -70,7 +79,7 @@ where

/// Returns the execution input.
#[inline]
pub(crate) fn exec_input(&self) -> &ExecutionInput<Args> {
pub(crate) fn exec_input(&self) -> &Option<ExecutionInput<Args>> {
&self.exec_input
}
}
Expand Down Expand Up @@ -202,6 +211,7 @@ where
CallBuilder {
env: Default::default(),
callee: Default::default(),
call_flags: Default::default(),
gas_limit: Default::default(),
transferred_value: Default::default(),
exec_input: Default::default(),
Expand All @@ -217,6 +227,7 @@ where
env: PhantomData<fn() -> E>,
/// The current parameters that have been built up so far.
callee: Callee,
call_flags: CallFlags,
gas_limit: GasLimit,
transferred_value: TransferredValue,
exec_input: Args,
Expand All @@ -238,6 +249,30 @@ where
CallBuilder {
env: Default::default(),
callee: Set(callee),
call_flags: self.call_flags,
gas_limit: self.gas_limit,
transferred_value: self.transferred_value,
exec_input: self.exec_input,
return_type: self.return_type,
}
}
}

impl<E, Callee, GasLimit, TransferredValue, Args, RetType>
CallBuilder<E, Callee, GasLimit, TransferredValue, Args, RetType>
where
E: Environment,
{
/// The flags used to change the behavior of the contract call.
#[inline]
pub fn call_flags(
self,
call_flags: CallFlags,
) -> CallBuilder<E, Callee, GasLimit, TransferredValue, Args, RetType> {
CallBuilder {
env: Default::default(),
callee: self.callee,
call_flags,
gas_limit: self.gas_limit,
transferred_value: self.transferred_value,
exec_input: self.exec_input,
Expand All @@ -260,6 +295,7 @@ where
CallBuilder {
env: Default::default(),
callee: self.callee,
call_flags: self.call_flags,
gas_limit: Set(gas_limit),
transferred_value: self.transferred_value,
exec_input: self.exec_input,
Expand All @@ -282,6 +318,7 @@ where
CallBuilder {
env: Default::default(),
callee: self.callee,
call_flags: self.call_flags,
gas_limit: self.gas_limit,
transferred_value: Set(transferred_value),
exec_input: self.exec_input,
Expand Down Expand Up @@ -324,6 +361,7 @@ where
CallBuilder {
env: Default::default(),
callee: self.callee,
call_flags: self.call_flags,
gas_limit: self.gas_limit,
transferred_value: self.transferred_value,
exec_input: self.exec_input,
Expand Down Expand Up @@ -359,6 +397,7 @@ where
CallBuilder {
env: Default::default(),
callee: self.callee,
call_flags: self.call_flags,
gas_limit: self.gas_limit,
transferred_value: self.transferred_value,
exec_input: Set(exec_input),
Expand All @@ -385,12 +424,42 @@ where
pub fn params(self) -> CallParams<E, Args, RetType> {
CallParams {
callee: self.callee.value(),
call_flags: self.call_flags,
gas_limit: self.gas_limit.unwrap_or_else(|| 0),
transferred_value: self
.transferred_value
.unwrap_or_else(|| E::Balance::from(0u32)),
_return_type: Default::default(),
exec_input: self.exec_input.value(),
exec_input: Some(self.exec_input.value()),
}
}
}

impl<E, GasLimit, TransferredValue, RetType>
CallBuilder<
E,
Set<E::AccountId>,
GasLimit,
TransferredValue,
Unset<ExecutionInput<EmptyArgumentList>>,
Unset<RetType>,
>
where
E: Environment,
GasLimit: Unwrap<Output = u64>,
TransferredValue: Unwrap<Output = E::Balance>,
{
/// Finalizes the call builder to call a function.
pub fn params(self) -> CallParams<E, EmptyArgumentList, ()> {
CallParams {
callee: self.callee.value(),
call_flags: self.call_flags,
gas_limit: self.gas_limit.unwrap_or_else(|| 0),
transferred_value: self
.transferred_value
.unwrap_or_else(|| E::Balance::from(0u32)),
_return_type: Default::default(),
exec_input: None,
}
}
}
Expand All @@ -416,6 +485,26 @@ where
}
}

impl<E, GasLimit, TransferredValue>
CallBuilder<
E,
Set<E::AccountId>,
GasLimit,
TransferredValue,
Unset<ExecutionInput<EmptyArgumentList>>,
Unset<ReturnType<()>>,
>
where
E: Environment,
GasLimit: Unwrap<Output = u64>,
TransferredValue: Unwrap<Output = E::Balance>,
{
/// Invokes the cross-chain function call.
pub fn fire(self) -> Result<(), Error> {
self.params().invoke()
}
}

impl<E, GasLimit, TransferredValue, Args, R>
CallBuilder<
E,
Expand Down
1 change: 1 addition & 0 deletions crates/env/src/engine/experimental_off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ impl TypedEnvBackend for EnvInstance {
{
let _gas_limit = params.gas_limit();
let _callee = params.callee();
let _call_flags = params.call_flags().into_u32();
let _transferred_value = params.transferred_value();
let _input = params.exec_input();
unimplemented!("off-chain environment does not support contract invocation")
Expand Down
1 change: 1 addition & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ impl TypedEnvBackend for EnvInstance {
{
let _gas_limit = params.gas_limit();
let _callee = params.callee();
let _call_flags = params.call_flags().into_u32();
let _transferred_value = params.transferred_value();
let _input = params.exec_input();
unimplemented!("off-chain environment does not support contract invocation")
Expand Down
Loading