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

E2E: Add runtime Call #1736

Merged
merged 15 commits into from
Apr 12, 2023
42 changes: 42 additions & 0 deletions crates/e2e/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,48 @@ where
})
}

/// Executes a runtime call `call_name` for the `pallet_name`.
/// The `call_data` is a `Vec<Value>`
///
/// Note:
/// - `pallet_name` must be in camel case, for example `Balances`.
/// - `call_name` must be snake case, for example `force_transfer`.
/// - `call_data` is a `Vec<subxt::dynamic::Value>` that holds a representation of
/// some value.
///
/// Returns when the transaction is included in a block. The return value
/// contains all events that are associated with this transaction.
pub async fn runtime_call<'a>(
&mut self,
signer: &Signer<C>,
pallet_name: &'a str,
call_name: &'a str,
call_data: Vec<Value>,
) -> Result<ExtrinsicEvents<C>, Error<C, E>> {
let tx_events = self
.api
.runtime_call(signer, pallet_name, call_name, call_data)
.await;

for evt in tx_events.iter() {
let evt = evt.unwrap_or_else(|err| {
panic!("unable to unwrap event: {err:?}");
});

if is_extrinsic_failed_event(&evt) {
let metadata = self.api.client.metadata();
let dispatch_error = subxt::error::DispatchError::decode_from(
evt.field_bytes(),
&metadata,
);
log_error(&format!("extrinsic for call failed: {dispatch_error:?}"));
return Err(Error::CallExtrinsic(dispatch_error))
}
}

Ok(tx_events)
}

/// Executes a dry-run `call`.
///
/// Returns the result of the dry run, together with the decoded return value of the
Expand Down
18 changes: 18 additions & 0 deletions crates/e2e/src/xts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,22 @@ where

self.submit_extrinsic(&call, signer).await
}

/// Submit an extrinsic `call_name` for the `pallet_name`.
/// The `call_data` is a `Vec<subxt::dynamic::Value>` that holds
/// a representation of some value.
///
/// Returns when the transaction is included in a block. The return value
/// contains all events that are associated with this transaction.
pub async fn runtime_call<'a>(
&self,
signer: &Signer<C>,
pallet_name: &'a str,
call_name: &'a str,
call_data: Vec<subxt::dynamic::Value>,
) -> ExtrinsicEvents<C> {
let call = subxt::dynamic::tx(pallet_name, call_name, call_data);

self.submit_extrinsic(&call, signer).await
}
}
9 changes: 9 additions & 0 deletions integration-tests/e2e-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
29 changes: 29 additions & 0 deletions integration-tests/e2e-call-runtime/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "e2e_call_runtime"
version = "4.1.0"
authors = ["Parity Technologies <[email protected]>"]
edition = "2021"
publish = false

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

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

[dev-dependencies]
ink_e2e = { path = "../../crates/e2e" }
subxt = { version = "0.27.1", default-features = false }

[lib]
path = "lib.rs"

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

#[ink::contract]
pub mod e2e_call_runtime {
#[ink(storage)]
#[derive(Default)]
pub struct Contract {}

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

#[ink(message)]
pub fn get_contract_balance(&self) -> Balance {
self.env().balance()
}
}

#[cfg(all(test, feature = "e2e-tests"))]
mod e2e_tests {
use super::*;
use ink_e2e::build_message;
use subxt::dynamic::Value;

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

#[ink_e2e::test]
async fn call_runtime_works(mut client: ink_e2e::Client<C, E>) -> E2EResult<()> {
// given
let constructor = ContractRef::new();
let contract_acc_id = client
.instantiate("e2e_call_runtime", &ink_e2e::alice(), constructor, 0, None)
.await
.expect("instantiate failed")
.account_id;

// when
let call_data = vec![
// A value representing a `MultiAddress<AccountId32, _>`. We want the "Id"
// variant, and that will ultimately contain the bytes
// for our destination address
Value::unnamed_variant("Id", [Value::from_bytes(&contract_acc_id)]),
// A value representing the amount we'd like to transfer.
Value::u128(100_000_000_000u128),
];

// Send funds from Alice to the contract using Balances::transfer
client
.runtime_call(&ink_e2e::alice(), "Balances", "transfer", call_data)
.await
.expect("runtime call failed");

// then
let get_balance = build_message::<ContractRef>(contract_acc_id.clone())
.call(|contract| contract.get_contract_balance());
let get_balance_res = client
.call_dry_run(&ink_e2e::alice(), &get_balance, 0, None)
.await;

assert!(matches!(
get_balance_res.return_value(),
100_000_000_000u128
));

Ok(())
}
}
}