Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
d6f55bc
Basic contract calling runtime
pmikolajczyk41 Feb 3, 2023
d3a1291
Offchain setting
pmikolajczyk41 Feb 3, 2023
7ad9137
Onchain env: reach seal function
pmikolajczyk41 Feb 3, 2023
6a47080
Feature-gating
pmikolajczyk41 Feb 6, 2023
1a6cb26
Pass call
pmikolajczyk41 Feb 6, 2023
6e21f63
Revert - e2e framework doesn't support custom environment
pmikolajczyk41 Feb 6, 2023
4918b82
Use just encodable argument
pmikolajczyk41 Feb 6, 2023
b9503cc
Pass decodable struct (however call is trapped :( )
pmikolajczyk41 Feb 6, 2023
5f5e2ae
Works with 0 transfer
pmikolajczyk41 Feb 6, 2023
ed4a155
Fix
pmikolajczyk41 Feb 6, 2023
9210619
Docs, remove leftovers
pmikolajczyk41 Feb 6, 2023
d80b3bb
Clippy
pmikolajczyk41 Feb 6, 2023
2e3d71c
docline
pmikolajczyk41 Feb 6, 2023
99982d4
Ignore test
pmikolajczyk41 Feb 6, 2023
9943cbe
typo
pmikolajczyk41 Feb 6, 2023
f825412
CHANGELOG.md, example README.md
pmikolajczyk41 Feb 6, 2023
2991153
Update CHANGELOG.md
ascjones Feb 6, 2023
2e7bcb4
Update crates/env/Cargo.toml
SkymanOne Feb 6, 2023
880b84c
Merge remote-tracking branch 'origin/master' into pmikolajczyk41/call…
pmikolajczyk41 Feb 7, 2023
4e69291
Syntactic improvements
pmikolajczyk41 Feb 8, 2023
0f8f142
Comparison to chain extension
pmikolajczyk41 Feb 8, 2023
cc13064
Indices explanation
pmikolajczyk41 Feb 8, 2023
93c535b
Reason for sp-io in deps
pmikolajczyk41 Feb 8, 2023
4ef1ce9
Rename enum variant. Docs. Return result. Failure e2e
pmikolajczyk41 Feb 9, 2023
a15dbce
Env API docs
pmikolajczyk41 Feb 9, 2023
e81fb98
spellcheck
pmikolajczyk41 Feb 9, 2023
9f59e7a
...
pmikolajczyk41 Feb 9, 2023
2285c87
Clean Cargo.toml
pmikolajczyk41 Feb 9, 2023
13225e2
Note about unstable host
pmikolajczyk41 Feb 9, 2023
d4665f9
Remove offline test
pmikolajczyk41 Feb 9, 2023
84dc23e
Rephrase note
pmikolajczyk41 Feb 9, 2023
ee7cfcb
Review
pmikolajczyk41 Feb 9, 2023
ca6238d
Doc about panic in off-chain env
pmikolajczyk41 Feb 9, 2023
89147f1
Add feature instead of ignoring
pmikolajczyk41 Feb 9, 2023
f3073de
Add feature instead of ignoring
pmikolajczyk41 Feb 9, 2023
e981d47
Error
pmikolajczyk41 Feb 10, 2023
db85259
Apply suggestions from code review
pmikolajczyk41 Feb 10, 2023
883739d
Rename variant error
pmikolajczyk41 Feb 10, 2023
dff8d92
PAnics
pmikolajczyk41 Feb 10, 2023
c3fe8f2
Warnign
pmikolajczyk41 Feb 10, 2023
614dae7
#[allow(clippy::enum_variant_names)]
pmikolajczyk41 Feb 10, 2023
315900a
uitest
pmikolajczyk41 Feb 10, 2023
1dfd5f5
uitest
pmikolajczyk41 Feb 10, 2023
560b954
Merge remote-tracking branch 'origin/master' into pmikolajczyk41/call…
pmikolajczyk41 Feb 13, 2023
23f71a5
Missing testcases
pmikolajczyk41 Feb 13, 2023
4671f0d
Update examples/call-runtime/lib.rs
pmikolajczyk41 Feb 14, 2023
59a0cb0
Update examples/call-runtime/lib.rs
pmikolajczyk41 Feb 14, 2023
3dcd483
Merge remote-tracking branch 'origin/master' into pmikolajczyk41/call…
pmikolajczyk41 Feb 14, 2023
27e0f0b
Merge branch 'master' into pmikolajczyk41/call_runtime
HCastano Feb 16, 2023
bf34580
Bump example version
HCastano Feb 16, 2023
f15c1b6
Fix some nitpicks
HCastano Feb 16, 2023
507e526
Rename error variant
pmikolajczyk41 Feb 17, 2023
fe98dfe
Remove allowance macro
pmikolajczyk41 Feb 17, 2023
b1f9845
Note
pmikolajczyk41 Feb 17, 2023
fcf4d85
Remove note
pmikolajczyk41 Feb 17, 2023
8284ddb
Merge remote-tracking branch 'origin/master' into pmikolajczyk41/call…
pmikolajczyk41 Feb 23, 2023
c81398f
Remove example
pmikolajczyk41 Feb 27, 2023
855f4e0
As integration test
pmikolajczyk41 Feb 27, 2023
fe76dda
Versions
pmikolajczyk41 Feb 27, 2023
3b4beb3
Fix changelog
pmikolajczyk41 Feb 27, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added
- `call-runtime` support

## Version 4.0.0-rc

The first release candidate is here! This is the first release which could become the final
Expand Down
1 change: 1 addition & 0 deletions crates/env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ std = [
"sha3",
"blake2",
]
call-runtime = []
# Enable contract debug messages via `debug_print!` and `debug_println!`.
ink-debug = []
18 changes: 18 additions & 0 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,3 +700,21 @@ 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 dispatch a pallet call.
///
/// # 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.
#[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`")
}
}
12 changes: 12 additions & 0 deletions crates/env/src/engine/on_chain/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ define_error_codes! {
/// 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.
CallRuntimeReturned = 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::CallRuntimeReturned => Self::CallRuntimeReturned,
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.
CallRuntimeReturned,
/// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature.
EcdsaRecoveryFailed,
}
Expand Down
1 change: 1 addition & 0 deletions crates/ink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ std = [
ink-debug = [
"ink_env/ink-debug",
]
call-runtime = ["ink_env/call-runtime"]
show-codegen-docs = []
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)
}
}
9 changes: 9 additions & 0 deletions examples/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
34 changes: 34 additions & 0 deletions examples/call-runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "call-runtime"
version = "4.0.0-rc"
authors = ["Parity Technologies <[email protected]>"]
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 }
sp-io = { version = "12.0.0", default-features = false, features = ["disable_panic_handler", "disable_oom", "disable_allocator"] }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is pretty ugly. Let's keep it for now, but can you explain why we need to have these particular feature flags

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is pretty ugly.

@HCastano I've created an issue to investigate it: #1681

Feel free to dig into it

sp-runtime = { version = "12.0.0", default-features = false }

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

[lib]
name = "call_runtime"
path = "lib.rs"
crate-type = ["cdylib"]

[features]
default = ["std"]
std = [
"ink/std",
"scale/std",
"scale-info/std",
"sp-runtime/std",
"sp-io/std",
]
ink-as-dependency = []
e2e-tests = []
19 changes: 19 additions & 0 deletions examples/call-runtime/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# `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:
`pallet_contracts::Config`:
```rust
impl pallet_contracts::Config for Runtime {
// `Everything` or anything that will allow for Balances::transfer extrinsic
type CallFilter = frame_support::traits::Everything;
type UnsafeUnstableInterface = ConstBool<true>;
}
```
155 changes: 155 additions & 0 deletions examples/call-runtime/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#![cfg_attr(not(feature = "std"), no_std)]

use ink::primitives::AccountId;
use sp_runtime::MultiAddress;

#[derive(scale::Encode)]
enum RuntimeCall {
#[codec(index = 4)]
Balances(BalancesCall),
}

#[derive(scale::Encode)]
enum BalancesCall {
#[codec(index = 0)]
Transfer {
dest: MultiAddress<AccountId, ()>,
#[codec(compact)]
value: u128,
},
}

#[ink::contract]
mod runtime_call {
use crate::{
BalancesCall,
RuntimeCall,
};

#[ink(storage)]
#[derive(Default)]
pub struct RuntimeCaller;

impl RuntimeCaller {
#[ink(constructor, payable)]
pub fn new() -> Self {
Self {}
}

#[ink(message)]
pub fn make_transfer_through_runtime(
&mut self,
receiver: AccountId,
value: Balance,
) {
self.env()
.call_runtime(&RuntimeCall::Balances(BalancesCall::Transfer {
dest: receiver.into(),
value,
}))
.expect("Should succeed");
}
}

#[cfg(test)]
mod tests {
use super::*;
use ink::env::{
test::default_accounts,
DefaultEnvironment,
};

#[ink::test]
#[should_panic(
expected = "off-chain environment does not support `call runtime`"
)]
fn cannot_call_runtime_off_chain() {
let mut contract = RuntimeCaller::new();
contract.make_transfer_through_runtime(
default_accounts::<DefaultEnvironment>().bob,
10,
);
}
}

#[cfg(all(test, feature = "e2e-tests"))]
mod e2e_tests {
use super::*;
use ink::{
env::{
test::default_accounts,
DefaultEnvironment,
},
primitives::AccountId,
};
use ink_e2e::build_message;

type E2EResult<T> = Result<T, Box<dyn std::error::Error>>;

const CONTRACT_BALANCE: Balance = 1_000_000_000_000_000;
const TRANSFER_VALUE: Balance = 1_000_000_000;

#[ink_e2e::test]
#[ignore = "Requires that the pallet contract is configured with:\
- `CallFilter` allowing for a transfer, e.g. `frame_support::traits::Everything`,\
- `UnsafeUnstableInterface = ConstBool<true>`"]
async fn it_works(mut client: Client<C, E>) -> E2EResult<()> {
// given
let constructor = RuntimeCallerRef::new();
let contract_acc_id = client
.instantiate(
"call-runtime",
&ink_e2e::alice(),
constructor,
CONTRACT_BALANCE,
None,
)
.await
.expect("instantiate failed")
.account_id;

let receiver: AccountId = default_accounts::<DefaultEnvironment>().bob;

let contract_balance_before = client
.balance(contract_acc_id)
.await
.expect("Failed to get account balance");
let receiver_balance_before = client
.balance(receiver)
.await
.expect("Failed to get account balance");

// when
let transfer_message = build_message::<RuntimeCallerRef>(contract_acc_id)
.call(|caller| {
caller.make_transfer_through_runtime(receiver, TRANSFER_VALUE)
});

let _call_res = client
.call(&ink_e2e::alice(), transfer_message, 0, None)
.await
.expect("call failed");

// then
let contract_balance_after = client
.balance(contract_acc_id)
.await
.expect("Failed to get account balance");
let receiver_balance_after = client
.balance(receiver)
.await
.expect("Failed to get account balance");

assert_eq!(
contract_balance_before,
contract_balance_after + TRANSFER_VALUE
);
assert_eq!(
receiver_balance_before + TRANSFER_VALUE,
receiver_balance_after
);

Ok(())
}
}
}