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

Add integration-test for calling a contract from a runtime pallet #2189

Merged
merged 21 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 15 additions & 3 deletions crates/e2e/sandbox/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,24 @@ impl<
macro_rules! create_sandbox {
($name:ident) => {
$crate::paste::paste! {
$crate::create_sandbox!($name, [<$name Runtime>], (), ());
$crate::create_sandbox!($name, [<$name Runtime>], (), (), {});
}
};
($name:ident, $chain_extension: ty, $debug: ty) => {
$crate::paste::paste! {
$crate::create_sandbox!($name, [<$name Runtime>], $chain_extension, $debug);
$crate::create_sandbox!($name, [<$name Runtime>], $chain_extension, $debug, {});
}
};
($sandbox:ident, $runtime:ident, $chain_extension: ty, $debug: ty) => {
($name:ident, $chain_extension: ty, $debug: ty, { $( $pallet_name:tt : $pallet:ident ),* $(,)? }) => {
$crate::paste::paste! {
$crate::create_sandbox!($name, [<$name Runtime>], $chain_extension, $debug, {
$(
$pallet_name : $pallet,
)*
});
}
};
($sandbox:ident, $runtime:ident, $chain_extension: ty, $debug: ty, { $( $pallet_name:tt : $pallet:ident ),* $(,)? }) => {


// Put all the boilerplate into an auxiliary module
Expand All @@ -113,6 +122,9 @@ mod construct_runtime {
Balances: $crate::pallet_balances,
Timestamp: $crate::pallet_timestamp,
Contracts: $crate::pallet_contracts,
$(
$pallet_name: $pallet,
)*
}
);

Expand Down
48 changes: 48 additions & 0 deletions integration-tests/public/runtime-call-contract/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
[workspace]
members = ["sandbox-runtime", "traits"]

[workspace.package]
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
homepage = "https://www.parity.io/"
keywords = ["wasm", "parity", "webassembly", "blockchain", "edsl"]
license = "Apache-2.0"
repository = "https://github.com/paritytech/ink"

[workspace.dependencies]
frame-support = { version = "31.0.0", default-features = false }
frame-system = { version = "31.0.0", default-features = false }
pallet-contracts = { version = "30.0.0", default-features = false }
codec = { package = "parity-scale-codec", version = "3.6.9", default-features = false }
scale-info = { version = "2.11.1", default-features = false }

[package]
name = "runtime-call-contract"
version = "5.0.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
publish = false

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

[dev-dependencies]
ink_e2e = { path = "../../../crates/e2e", features = ["sandbox"] }
sandbox-runtime = { path = "sandbox-runtime", default-features = false }
scale-value = "0.14.1"
# can't use workspace dependency because of `cargo-contract` build not
# working with workspace dependencies
frame-support = { version = "31.0.0", default-features = false }

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
"sandbox-runtime/std",
"flipper-traits/std",
]
ink-as-dependency = []
60 changes: 60 additions & 0 deletions integration-tests/public/runtime-call-contract/e2e_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use super::{
Flipper,
FlipperRef,
};
use ink_e2e::{
ChainBackend,
ContractsBackend,
};

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

/// Just instantiate a contract using non-default runtime.
#[ink_e2e::test(backend(runtime_only(sandbox = sandbox_runtime::ContractCallerSandbox)))]
async fn instantiate_and_get<Client: E2EBackend>(mut client: Client) -> E2EResult<()> {
use flipper_traits::Flip;

let initial_value = false;
let mut constructor = FlipperRef::new(initial_value);

let contract = client
.instantiate("runtime-call-contract", &ink_e2e::alice(), &mut constructor)
.submit()
.await
.expect("instantiate failed");

let mut call_builder = contract.call_builder::<Flipper>();
let flip_dry_run = client
.call(&ink_e2e::bob(), &call_builder.flip())
.dry_run()
.await?;
let gas_required = flip_dry_run.exec_result.gas_required;

// call pallet dispatchable
client
.runtime_call(
&ink_e2e::alice(),
"ContractCaller",
"contract_call_flip",
vec![
scale_value::Value::from_bytes(contract.account_id),
scale_value::serde::to_value(frame_support::weights::Weight::from_parts(
gas_required.ref_time(),
gas_required.proof_size(),
))
.unwrap(),
],
)
.await
.expect("runtime call failed");

// now check that the flip was executed via the pallet
let get_result = client
.call(&ink_e2e::alice(), &call_builder.get())
.dry_run()
.await?;

assert_eq!(get_result.return_value(), !initial_value);

Ok(())
}
36 changes: 36 additions & 0 deletions integration-tests/public/runtime-call-contract/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]

pub use flipper::{
Flipper,
FlipperRef,
};

#[ink::contract]
mod flipper {
#[ink(storage)]
pub struct Flipper {
value: bool,
}

impl Flipper {
#[ink(constructor)]
pub fn new(init_value: bool) -> Self {
Self { value: init_value }
}
}

impl flipper_traits::Flip for Flipper {
#[ink(message)]
fn flip(&mut self) {
self.value = !self.value;
}

#[ink(message)]
fn get(&self) -> bool {
self.value
}
}
}

#[cfg(test)]
mod e2e_tests;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "sandbox-runtime"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
repository.workspace = true

[dependencies]
ink_sandbox = { path = "../../../../crates/e2e/sandbox" }
pallet-contract-caller = { path = "pallet-contract-caller", default-features = false }
frame-support = { workspace = true }
frame-system = { workspace = true }
codec = { workspace = true }
scale-info = { workspace = true }

[features]
default = ["std"]
std = [
"pallet-contract-caller/std",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[package]
name = "pallet-contract-caller"
version = "0.1.0"
description = "Demonstrate calling an ink! contract from a pallet"
authors = ["Substrate DevHub <https://github.com/substrate-developer-hub>"]
homepage = "https://substrate.io"
edition.workspace = true
license = "MIT-0"
publish = false
repository = "https://github.com/paritytech/ink"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { workspace = true, default-features = false, features = ["derive"] }
scale-info = { workspace = true, default-features = false, features = ["derive"] }
frame-support = { workspace = true, default-features = false }
frame-system = { workspace = true, default-features = false }

pallet-contracts = { workspace = true, default-features = false }
flipper-traits = { path = "../../traits", default-features = false }
ink = { path = "../../../../../crates/ink", features = ["no-panic-handler", "no-allocator"] }

[features]
default = ["std"]
std = [
"codec/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"pallet-contracts/std",
"ink/std"
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! # Contract Caller
//!
//! Demonstrates calling into an `ink!` contract from a pallet.
#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use flipper_traits::Flip;
use frame_support::{
pallet_prelude::*,
traits::fungible::Inspect,
};
use frame_system::pallet_prelude::*;
use ink::codegen::TraitCallBuilder;

type AccountIdOf<Runtime> = <Runtime as frame_system::Config>::AccountId;

#[pallet::pallet]
pub struct Pallet<T>(_);

#[pallet::config]
pub trait Config: frame_system::Config + pallet_contracts::Config {}

#[pallet::error]
pub enum Error<T> {}

#[pallet::call]
impl<T: Config> Pallet<T>
where
[u8; 32]: From<<T as frame_system::Config>::AccountId>,
<<T as pallet_contracts::Config>::Currency as Inspect<
<T as frame_system::Config>::AccountId,
>>::Balance: From<u128>,
{
/// Call the flip method on the contract at the given `contract` account.
#[pallet::call_index(0)]
#[pallet::weight(<T::WeightInfo as pallet_contracts::WeightInfo>::call().saturating_add(*gas_limit))]
pub fn contract_call_flip(
origin: OriginFor<T>,
contract: AccountIdOf<T>,
gas_limit: Weight,
) -> DispatchResult {
let who = ensure_signed(origin)?;

let ink_account_id =
ink::primitives::AccountId::from(<[u8; 32]>::from(contract.clone()));
let mut flipper: ink::contract_ref!(Flip, ink::env::DefaultEnvironment) =
ink_account_id.into();
let call_builder = flipper.call_mut();

let params = call_builder
.flip()
.ref_time_limit(gas_limit.ref_time())
.proof_size_limit(gas_limit.proof_size())
.params();

// Next step is to explore ways to encapsulate the following into the call
// builder.
let value = *params.transferred_value();
let data = params.exec_input().encode();
let weight =
Weight::from_parts(params.ref_time_limit(), params.proof_size_limit());
let storage_deposit_limit =
params.storage_deposit_limit().map(|limit| (*limit).into());

let result = pallet_contracts::Pallet::<T>::bare_call(
who.clone(),
contract.clone(),
value.into(),
weight,
storage_deposit_limit,
data,
pallet_contracts::DebugInfo::UnsafeDebug,
pallet_contracts::CollectEvents::Skip,
pallet_contracts::Determinism::Enforced,
);

assert!(!result.result?.did_revert());

Ok(())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! # Contract Caller
//!
//! Demonstrates calling into an `ink!` contract from a pallet.

#![cfg_attr(not(feature = "std"), no_std)]

ink_sandbox::create_sandbox!(ContractCallerSandbox, ContractCallerSandboxRuntime, (), (), {
ContractCaller: pallet_contract_caller,
});

impl pallet_contract_caller::Config for ContractCallerSandboxRuntime {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "flipper-traits"
version = "5.0.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
publish = false

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

[lib]
path = "lib.rs"

[features]
default = ["std"]
std = [
"ink/std",
]
14 changes: 14 additions & 0 deletions integration-tests/public/runtime-call-contract/traits/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![cfg_attr(not(feature = "std"), no_std, no_main)]

/// Allows to flip and get a bool value.
#[ink::trait_definition]
pub trait Flip {
/// Flip the value of the stored `bool` from `true`
/// to `false` and vice versa.
#[ink(message)]
fn flip(&mut self);

/// Returns the current value of our `bool`.
#[ink(message)]
fn get(&self) -> bool;
}