Skip to content

Commit

Permalink
Add integration-test for calling a contract from a runtime pallet (#…
Browse files Browse the repository at this point in the history
…2189)

* Add files for integration test

* WIP call contract from runtime

* mv to new folder structure

* basic e2e test compiles and works

* WIP register pallet

* WIP add pallets to sandbox runtime

* Add pallet to own crate

* WIP rearrange workspace

* Fix errors

* Fix imports

* Fix contract

* Fix contract manifest

* Test passes

* Copy full test over

* Implement traits to fix test

* revert

* Remove println!

* Put contract back at workspace root again
  • Loading branch information
ascjones authored Apr 8, 2024
1 parent f107a92 commit 06914b0
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 3 deletions.
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 {}
18 changes: 18 additions & 0 deletions integration-tests/public/runtime-call-contract/traits/Cargo.toml
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;
}

0 comments on commit 06914b0

Please sign in to comment.