Skip to content

Commit

Permalink
Support call_runtime (#1641)
Browse files Browse the repository at this point in the history
* Basic contract calling runtime

* Offchain setting

* Onchain env: reach seal function

* Feature-gating

* Pass call

* Revert - e2e framework doesn't support custom environment

* Use just encodable argument

* Pass decodable struct (however call is trapped :( )

* Works with 0 transfer

* Fix

* Docs, remove leftovers

* Clippy

* docline

* Ignore test

* typo

* CHANGELOG.md, example README.md

* Update CHANGELOG.md

Co-authored-by: German <german.nikolishin@gmail.com>

* Update crates/env/Cargo.toml

* Syntactic improvements

* Comparison to chain extension

* Indices explanation

* Reason for sp-io in deps

* Rename enum variant. Docs. Return result. Failure e2e

* Env API docs

* spellcheck

* ...

* Clean Cargo.toml

* Note about unstable host

* Remove offline test

* Rephrase note

* Review

* Doc about panic in off-chain env

* Add feature instead of ignoring

* Add feature instead of ignoring

* Error

* Apply suggestions from code review

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Rename variant error

* PAnics

* Warnign

* #[allow(clippy::enum_variant_names)]

* uitest

* uitest

* Missing testcases

* Update examples/call-runtime/lib.rs

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Update examples/call-runtime/lib.rs

Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>

* Bump example version

* Fix some nitpicks

* Rename error variant

* Remove allowance macro

* Note

* Remove note

* Remove example

* As integration test

* Versions

* Fix changelog

---------

Co-authored-by: Andrew Jones <ascjones@gmail.com>
Co-authored-by: German <german.nikolishin@gmail.com>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
Co-authored-by: Hernando Castano <HCastano@users.noreply.github.com>
Co-authored-by: Hernando Castano <hernando@hcastano.com>
  • Loading branch information
6 people authored Feb 27, 2023
1 parent e9321aa commit 93ee7fb
Show file tree
Hide file tree
Showing 15 changed files with 497 additions and 3 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Fixing `ManualKey<0>` to act properly - [#1670](https://github.com/paritytech/ink/pull/1670)

### Added
- Add `call-runtime` support - [#1641](https://github.com/paritytech/ink/pull/1641)

## Version 4.0.0

The latest stable release of ink! is here 🥳
Expand Down Expand Up @@ -50,7 +53,6 @@ compatible with the ink! `4.0.0` release.

For full compatibility requirements see the [migration guide](https://use.ink/faq/migrating-from-ink-3-to-4/#compatibility).

### Added
- Add `Mapping::contains(key)` and `Mapping::insert_return_size(key, val)`[#1224](https://github.com/paritytech/ink/pull/1224)
- Add [`payment-channel`](https://github.com/paritytech/ink/tree/master/examples/payment-channel) example ‒ [#1248](https://github.com/paritytech/ink/pull/1248) (thanks [@kanishkatn](https://github.com/kanishkatn)!)
- Add `version` field to ink! metadata ‒ [#1313](https://github.com/paritytech/ink/pull/1313)
Expand Down
3 changes: 3 additions & 0 deletions crates/env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ std = [
"blake2",
]

# Enable direct call to a pallet dispatchable via `call_runtime()`.
call-runtime = []

# Enable contract debug messages via `debug_print!` and `debug_println!`.
ink-debug = []

Expand Down
32 changes: 32 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,35 @@ where
pub fn set_code_hash(code_hash: &[u8; 32]) -> Result<()> {
<EnvInstance as OnInstance>::on_instance(|instance| instance.set_code_hash(code_hash))
}

/// Tries to trigger a runtime dispatchable, i.e. an extrinsic from a pallet.
///
/// `call` (after SCALE encoding) should be decodable to a valid instance of `RuntimeCall` enum.
///
/// For more details consult
/// [host function documentation](https://paritytech.github.io/substrate/master/pallet_contracts/api_doc/trait.Current.html#tymethod.call_runtime).
///
/// # Errors
///
/// - If the call cannot be properly decoded on the pallet contracts side.
/// - If the runtime doesn't allow for the contract unstable feature.
/// - If the runtime doesn't allow for dispatching this call from a contract.
///
/// # Note
///
/// The `call_runtime` host function is still part of `pallet-contracts`' unstable interface and
/// thus can be changed at anytime.
///
/// # Panics
///
/// Panics in the off-chain environment.
#[cfg(feature = "call-runtime")]
pub fn call_runtime<E, Call>(call: &Call) -> Result<()>
where
E: Environment,
Call: scale::Encode,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
TypedEnvBackend::call_runtime::<E, _>(instance, call)
})
}
6 changes: 6 additions & 0 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,4 +510,10 @@ pub trait TypedEnvBackend: EnvBackend {
fn own_code_hash<E>(&mut self) -> Result<E::Hash>
where
E: Environment;

#[cfg(feature = "call-runtime")]
fn call_runtime<E, Call>(&mut self, call: &Call) -> Result<()>
where
E: Environment,
Call: scale::Encode;
}
8 changes: 8 additions & 0 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -541,4 +541,12 @@ impl TypedEnvBackend for EnvInstance {
{
unimplemented!("off-chain environment does not support `own_code_hash`")
}

#[cfg(feature = "call-runtime")]
fn call_runtime<E, Call>(&mut self, _call: &Call) -> Result<()>
where
E: Environment,
{
unimplemented!("off-chain environment does not support `call_runtime`")
}
}
14 changes: 13 additions & 1 deletion crates/env/src/engine/on_chain/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,11 @@ define_error_codes! {
CodeNotFound = 7,
/// The account that was called is no contract.
NotCallable = 8,
/// The call to `debug_message` had no effect because debug message
/// The call to `debug_message` had no effect because debug message
/// recording was disabled.
LoggingDisabled = 9,
/// The call dispatched by `call_runtime` was executed but returned an error.
CallRuntimeFailed = 10,
/// ECDSA public key recovery failed. Most probably wrong recovery id or signature.
EcdsaRecoveryFailed = 11,
}
Expand Down Expand Up @@ -325,6 +327,9 @@ mod sys {
out_ptr: Ptr32Mut<[u8]>,
out_len_ptr: Ptr32Mut<u32>,
) -> ReturnCode;

#[cfg(feature = "call-runtime")]
pub fn call_runtime(call_ptr: Ptr32<[u8]>, call_len: u32) -> ReturnCode;
}

#[link(wasm_import_module = "seal1")]
Expand Down Expand Up @@ -639,6 +644,13 @@ pub fn return_value(flags: ReturnFlags, return_value: &[u8]) -> ! {
}
}

#[cfg(feature = "call-runtime")]
pub fn call_runtime(call: &[u8]) -> Result {
let ret_code =
unsafe { sys::call_runtime(Ptr32::from_slice(call), call.len() as u32) };
ret_code.into()
}

macro_rules! impl_wrapper_for {
( $( $name:ident, )* ) => {
$(
Expand Down
12 changes: 12 additions & 0 deletions crates/env/src/engine/on_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ impl From<ext::Error> for Error {
ext::Error::CodeNotFound => Self::CodeNotFound,
ext::Error::NotCallable => Self::NotCallable,
ext::Error::LoggingDisabled => Self::LoggingDisabled,
ext::Error::CallRuntimeFailed => Self::CallRuntimeFailed,
ext::Error::EcdsaRecoveryFailed => Self::EcdsaRecoveryFailed,
}
}
Expand Down Expand Up @@ -575,4 +576,15 @@ impl TypedEnvBackend for EnvInstance {
let hash = scale::Decode::decode(&mut &output[..])?;
Ok(hash)
}

#[cfg(feature = "call-runtime")]
fn call_runtime<E, Call>(&mut self, call: &Call) -> Result<()>
where
E: Environment,
Call: scale::Encode,
{
let mut scope = self.scoped_buffer();
let enc_call = scope.take_encoded(call);
ext::call_runtime(enc_call).map_err(Into::into)
}
}
2 changes: 2 additions & 0 deletions crates/env/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ pub enum Error {
/// The call to `debug_message` had no effect because debug message
/// recording was disabled.
LoggingDisabled,
/// The call dispatched by `call_runtime` was executed but returned an error.
CallRuntimeFailed,
/// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature.
EcdsaRecoveryFailed,
}
Expand Down
6 changes: 6 additions & 0 deletions crates/ink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ std = [
ink-debug = [
"ink_env/ink-debug",
]

# Enable direct call to a pallet dispatchable via `call_runtime()`.
call-runtime = [
"ink_env/call-runtime",
]

show-codegen-docs = []

# Disable the ink! provided global memory allocator.
Expand Down
5 changes: 5 additions & 0 deletions crates/ink/src/env_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -969,4 +969,9 @@ where
pub fn own_code_hash(self) -> Result<E::Hash> {
ink_env::own_code_hash::<E>()
}

#[cfg(feature = "call-runtime")]
pub fn call_runtime<Call: scale::Encode>(self, call: &Call) -> Result<()> {
ink_env::call_runtime::<E, _>(call)
}
}
6 changes: 5 additions & 1 deletion crates/storage/src/lazy/mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ where
/// Removes the `value` at `key`, returning the previous `value` at `key` from storage.
///
/// Returns `None` if no `value` exists at the given `key`.
/// **WARNING**: this method uses the [unstable interface](https://github.com/paritytech/substrate/tree/master/frame/contracts#unstable-interfaces),
///
/// # Warning
///
/// This method uses the
/// [unstable interface](https://github.com/paritytech/substrate/tree/master/frame/contracts#unstable-interfaces),
/// which is unsafe and normally is not available on production chains.
#[inline]
pub fn take<Q>(&self, key: Q) -> Option<V>
Expand Down
9 changes: 9 additions & 0 deletions integration-tests/call-runtime/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Ignore build artifacts from the local tests sub-crate.
/target/

# Ignore backup files creates by cargo fmt.
**/*.rs.bk

# Remove Cargo.lock when creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
43 changes: 43 additions & 0 deletions integration-tests/call-runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[package]
name = "call-runtime"
version = "4.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
publish = false

[dependencies]
ink = { path = "../../crates/ink", default-features = false, features = ["call-runtime"] }

scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
scale-info = { version = "2.3", default-features = false, features = ["derive"], optional = true }

# Substrate
#
# We need to explicitly turn off some of the `sp-io` features, to avoid conflicts
# (especially for global allocator).
#
# See also: https://substrate.stackexchange.com/questions/4733/error-when-compiling-a-contract-using-the-xcm-chain-extension.
sp-io = { version = "18.0.0", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] }
sp-runtime = { version = "19.0.0", default-features = false }

[dev-dependencies]
ink_e2e = { path = "../../crates/e2e" }

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
"scale/std",
"scale-info/std",
"sp-runtime/std",
"sp-io/std",
]
ink-as-dependency = []
e2e-tests = []

# Assumes that the node used in E2E testing allows using the `call-runtime` API, including triggering
# `Balances::transfer` extrinsic.
permissive-node = []
29 changes: 29 additions & 0 deletions integration-tests/call-runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# `call-runtime` example

## What is this example about?

It demonstrates how to call a runtime dispatchable from an ink! contract.

## Chain-side configuration

To integrate this example into Substrate you need to adjust pallet contracts configuration in your runtime:
```rust
// In your node's runtime configuration file (runtime.rs)
impl pallet_contracts::Config for Runtime {
// `Everything` or anything that will allow for the `Balances::transfer` extrinsic.
type CallFilter = frame_support::traits::Everything;
type UnsafeUnstableInterface = ConstBool<true>;
}
```

## Comparison to `ChainExtension`

Just as a chain extension, `call_runtime` API allows contracts for direct calling to the runtime.
You can trigger any extrinsic that is not forbidden by `pallet_contracts::Config::CallFilter`.
Consider writing a chain extension if you need to perform one of the following tasks:
- Return data.
- Provide functionality **exclusively** to contracts.
- Provide custom weights.
- Avoid the need to keep the `Call` data structure stable.
Loading

0 comments on commit 93ee7fb

Please sign in to comment.