From 5f16df28ed8025484444d6987da34528c9b65fe1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Tomasz=20Drwi=C4=99ga?= <tomusdrw@users.noreply.github.com>
Date: Mon, 5 Apr 2021 19:48:47 +0200
Subject: [PATCH] CLI: Encode Call & Multiple Bridge Instances. (#859)

* Encode Call & Multiple Bridge Instances.

* Remove redundant clone.

* Fix comment.

* Rename pallet index bridge instance index.

* Update error messages related to target instances

Co-authored-by: Hernando Castano <hernando@hcastano.com>
---
 .../bin-substrate/src/cli/encode_call.rs      | 249 ++++++++++++++++++
 bridges/relays/bin-substrate/src/cli/mod.rs   |  47 ++--
 .../bin-substrate/src/rialto_millau/cli.rs    |  72 +----
 .../bin-substrate/src/rialto_millau/mod.rs    | 235 +++++++----------
 4 files changed, 367 insertions(+), 236 deletions(-)
 create mode 100644 bridges/relays/bin-substrate/src/cli/encode_call.rs

diff --git a/bridges/relays/bin-substrate/src/cli/encode_call.rs b/bridges/relays/bin-substrate/src/cli/encode_call.rs
new file mode 100644
index 00000000000..3afe8d43b94
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/cli/encode_call.rs
@@ -0,0 +1,249 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+use crate::cli::{AccountId, Balance, CliChain, ExplicitOrMaximal, HexBytes, HexLaneId};
+use frame_support::dispatch::GetDispatchInfo;
+use relay_substrate_client::Chain;
+use structopt::{clap::arg_enum, StructOpt};
+
+/// Encode source chain runtime call.
+#[derive(StructOpt)]
+pub struct EncodeCall {
+	/// A bridge instance to encode call for.
+	#[structopt(possible_values = &EncodeCallBridge::variants(), case_insensitive = true)]
+	bridge: EncodeCallBridge,
+	#[structopt(flatten)]
+	call: Call,
+}
+
+/// All possible messages that may be delivered to generic Substrate chain.
+///
+/// Note this enum may be used in the context of both Source (as part of `encode-call`)
+/// and Target chain (as part of `encode-message/send-message`).
+#[derive(StructOpt, Debug)]
+pub enum Call {
+	/// Raw bytes for the message
+	Raw {
+		/// Raw, SCALE-encoded message
+		data: HexBytes,
+	},
+	/// Make an on-chain remark (comment).
+	Remark {
+		/// Explicit remark payload.
+		#[structopt(long, conflicts_with("remark_size"))]
+		remark_payload: HexBytes,
+		/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
+		#[structopt(long, conflicts_with("remark_payload"))]
+		remark_size: Option<ExplicitOrMaximal<usize>>,
+	},
+	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
+	Transfer {
+		/// Address of an account to receive the transfer.
+		#[structopt(long)]
+		recipient: AccountId,
+		/// Amount of target tokens to send in target chain base currency units.
+		#[structopt(long)]
+		amount: Balance,
+	},
+	/// A call to the specific Bridge Messages pallet to queue message to be sent over a bridge.
+	BridgeSendMessage {
+		/// An index of the bridge instance which represents the expected target chain.
+		#[structopt(skip = 255)]
+		bridge_instance_index: u8,
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Raw SCALE-encoded Message Payload to submit to the messages pallet.
+		///
+		/// This can be obtained by encoding call for the target chain.
+		#[structopt(long)]
+		payload: HexBytes,
+		/// Declared delivery and dispatch fee in base source-chain currency units.
+		#[structopt(long)]
+		fee: Balance,
+	},
+}
+
+pub trait CliEncodeCall: Chain {
+	/// Maximal size (in bytes) of any extrinsic (from the runtime).
+	fn max_extrinsic_size() -> u32;
+
+	/// Encode a CLI call.
+	fn encode_call(call: &Call) -> anyhow::Result<Self::Call>;
+}
+
+arg_enum! {
+	#[derive(Debug)]
+	/// Bridge to encode call for.
+	pub enum EncodeCallBridge {
+		MillauToRialto,
+		RialtoToMillau,
+	}
+}
+
+impl EncodeCallBridge {
+	fn bridge_instance_index(&self) -> u8 {
+		match self {
+			Self::MillauToRialto => MILLAU_TO_RIALTO_INDEX,
+			Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
+		}
+	}
+}
+
+pub const RIALTO_TO_MILLAU_INDEX: u8 = 0;
+pub const MILLAU_TO_RIALTO_INDEX: u8 = 0;
+
+macro_rules! select_bridge {
+	($bridge: expr, $generic: tt) => {
+		match $bridge {
+			EncodeCallBridge::MillauToRialto => {
+				type Source = relay_millau_client::Millau;
+				type Target = relay_rialto_client::Rialto;
+
+				$generic
+			}
+			EncodeCallBridge::RialtoToMillau => {
+				type Source = relay_rialto_client::Rialto;
+				type Target = relay_millau_client::Millau;
+
+				$generic
+			}
+		}
+	};
+}
+
+impl EncodeCall {
+	fn encode(&mut self) -> anyhow::Result<HexBytes> {
+		select_bridge!(self.bridge, {
+			preprocess_call::<Source, Target>(&mut self.call, self.bridge.bridge_instance_index());
+			let call = Source::encode_call(&self.call)?;
+
+			let encoded = HexBytes::encode(&call);
+
+			log::info!(target: "bridge", "Generated {} call: {:#?}", Source::NAME, call);
+			log::info!(target: "bridge", "Weight of {} call: {}", Source::NAME, call.get_dispatch_info().weight);
+			log::info!(target: "bridge", "Encoded {} call: {:?}", Source::NAME, encoded);
+
+			Ok(encoded)
+		})
+	}
+
+	/// Run the command.
+	pub async fn run(mut self) -> anyhow::Result<()> {
+		println!("{:?}", self.encode()?);
+		Ok(())
+	}
+}
+
+/// Prepare the call to be passed to [`CliEncodeCall::encode_call`].
+///
+/// This function will fill in all optional and missing pieces and will make sure that
+/// values are converted to bridge-specific ones.
+///
+/// Most importantly, the method will fill-in [`bridge_instance_index`] parameter for
+/// target-chain specific calls.
+pub(crate) fn preprocess_call<Source: CliEncodeCall + CliChain, Target: CliEncodeCall>(
+	call: &mut Call,
+	bridge_instance: u8,
+) {
+	match *call {
+		Call::Raw { .. } => {}
+		Call::Remark {
+			ref remark_size,
+			ref mut remark_payload,
+		} => {
+			if remark_payload.0.is_empty() {
+				*remark_payload = HexBytes(generate_remark_payload(
+					&remark_size,
+					compute_maximal_message_arguments_size(Source::max_extrinsic_size(), Target::max_extrinsic_size()),
+				));
+			}
+		}
+		Call::Transfer { ref mut recipient, .. } => {
+			recipient.enforce_chain::<Source>();
+		}
+		Call::BridgeSendMessage {
+			ref mut bridge_instance_index,
+			..
+		} => {
+			*bridge_instance_index = bridge_instance;
+		}
+	};
+}
+
+fn generate_remark_payload(remark_size: &Option<ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
+	match remark_size {
+		Some(ExplicitOrMaximal::Explicit(remark_size)) => vec![0; *remark_size],
+		Some(ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
+		None => format!(
+			"Unix time: {}",
+			std::time::SystemTime::now()
+				.duration_since(std::time::SystemTime::UNIX_EPOCH)
+				.unwrap_or_default()
+				.as_secs(),
+		)
+		.as_bytes()
+		.to_vec(),
+	}
+}
+
+pub(crate) fn compute_maximal_message_arguments_size(
+	maximal_source_extrinsic_size: u32,
+	maximal_target_extrinsic_size: u32,
+) -> u32 {
+	// assume that both signed extensions and other arguments fit 1KB
+	let service_tx_bytes_on_source_chain = 1024;
+	let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
+	let maximal_call_size =
+		bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
+	let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
+		maximal_source_extrinsic_size
+	} else {
+		maximal_call_size
+	};
+
+	// bytes in Call encoding that are used to encode everything except arguments
+	let service_bytes = 1 + 1 + 4;
+	maximal_call_size - service_bytes
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+
+	#[test]
+	fn should_encode_transfer_call() {
+		// given
+		let mut encode_call = EncodeCall::from_iter(vec![
+			"encode-call",
+			"RialtoToMillau",
+			"transfer",
+			"--amount",
+			"12345",
+			"--recipient",
+			"5sauUXUfPjmwxSgmb3tZ5d6yx24eZX4wWJ2JtVUBaQqFbvEU",
+		]);
+
+		// when
+		let hex = encode_call.encode().unwrap();
+
+		// then
+		assert_eq!(
+			format!("{:?}", hex),
+			"0x0d00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
+		);
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/cli/mod.rs b/bridges/relays/bin-substrate/src/cli/mod.rs
index f3f7dfefd9f..216b778c218 100644
--- a/bridges/relays/bin-substrate/src/cli/mod.rs
+++ b/bridges/relays/bin-substrate/src/cli/mod.rs
@@ -25,6 +25,8 @@ use frame_support::weights::Weight;
 use sp_runtime::app_crypto::Ss58Codec;
 use structopt::{clap::arg_enum, StructOpt};
 
+pub(crate) mod encode_call;
+
 mod derive_account;
 mod init_bridge;
 mod relay_headers;
@@ -63,7 +65,7 @@ pub enum Command {
 	///
 	/// The call can be used either as message payload or can be wrapped into a transaction
 	/// and executed on the chain directly.
-	EncodeCall(EncodeCall),
+	EncodeCall(encode_call::EncodeCall),
 	/// Generate SCALE-encoded `MessagePayload` object that can be sent over selected bridge.
 	///
 	/// The `MessagePayload` can be then fed to `Messages::send_message` function and sent over
@@ -109,23 +111,6 @@ impl SendMessage {
 	}
 }
 
-/// A call to encode.
-#[derive(StructOpt)]
-pub enum EncodeCall {
-	#[structopt(flatten)]
-	RialtoMillau(rialto_millau::EncodeCall),
-}
-
-impl EncodeCall {
-	/// Run the command.
-	pub async fn run(self) -> anyhow::Result<()> {
-		match self {
-			Self::RialtoMillau(arg) => arg.run().await?,
-		}
-		Ok(())
-	}
-}
-
 /// A `MessagePayload` to encode.
 #[derive(StructOpt)]
 pub enum EncodeMessagePayload {
@@ -273,9 +258,6 @@ pub trait CliChain: relay_substrate_client::Chain {
 	/// Numeric value of SS58 format.
 	fn ss58_format() -> u16;
 
-	/// Parse CLI call and encode it to be dispatched on this specific chain.
-	fn encode_call(call: crate::rialto_millau::cli::Call) -> Result<Self::Call, String>;
-
 	/// Construct message payload to be sent over the bridge.
 	fn encode_message(message: crate::rialto_millau::cli::MessagePayload) -> Result<Self::MessagePayload, String>;
 
@@ -304,7 +286,7 @@ impl std::str::FromStr for HexLaneId {
 }
 
 /// Nicer formatting for raw bytes vectors.
-#[derive(Encode, Decode)]
+#[derive(Default, Encode, Decode)]
 pub struct HexBytes(pub Vec<u8>);
 
 impl std::str::FromStr for HexBytes {
@@ -317,7 +299,13 @@ impl std::str::FromStr for HexBytes {
 
 impl std::fmt::Debug for HexBytes {
 	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
-		write!(fmt, "0x{}", hex::encode(&self.0))
+		write!(fmt, "0x{}", self)
+	}
+}
+
+impl std::fmt::Display for HexBytes {
+	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+		write!(fmt, "{}", hex::encode(&self.0))
 	}
 }
 
@@ -470,4 +458,17 @@ mod tests {
 
 		assert_eq!(actual, expected)
 	}
+
+	#[test]
+	fn hex_bytes_display_matches_from_str_for_clap() {
+		// given
+		let hex = HexBytes(vec![1, 2, 3, 4]);
+		let display = format!("{}", hex);
+
+		// when
+		let hex2: HexBytes = display.parse().unwrap();
+
+		// then
+		assert_eq!(hex.0, hex2.0);
+	}
 }
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/cli.rs b/bridges/relays/bin-substrate/src/rialto_millau/cli.rs
index 2f111565373..9e5788b1cb6 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/cli.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/cli.rs
@@ -48,7 +48,7 @@ pub enum SendMessage {
 		fee: Option<Balance>,
 		/// Message type.
 		#[structopt(subcommand)]
-		message: Call,
+		message: crate::cli::encode_call::Call,
 		/// The origin to use when dispatching the message on the target chain. Defaults to
 		/// `SourceAccount`.
 		#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
@@ -73,7 +73,7 @@ pub enum SendMessage {
 		fee: Option<Balance>,
 		/// Message type.
 		#[structopt(subcommand)]
-		message: Call,
+		message: crate::cli::encode_call::Call,
 		/// The origin to use when dispatching the message on the target chain. Defaults to
 		/// `SourceAccount`.
 		#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
@@ -89,31 +89,6 @@ impl SendMessage {
 	}
 }
 
-/// A call to encode.
-///
-/// TODO [#855] Move to separate module.
-#[derive(StructOpt)]
-pub enum EncodeCall {
-	/// Encode Rialto's Call.
-	Rialto {
-		#[structopt(flatten)]
-		call: Call,
-	},
-	/// Encode Millau's Call.
-	Millau {
-		#[structopt(flatten)]
-		call: Call,
-	},
-}
-
-impl EncodeCall {
-	/// Run the command.
-	pub async fn run(self) -> anyhow::Result<()> {
-		super::run_encode_call(self).await.map_err(format_err)?;
-		Ok(())
-	}
-}
-
 /// A `MessagePayload` to encode.
 ///
 /// TODO [#855] Move to separate module.
@@ -192,50 +167,9 @@ pub enum MessagePayload {
 	Call {
 		/// Message details.
 		#[structopt(flatten)]
-		call: Call,
+		call: crate::cli::encode_call::Call,
 		/// SS58 encoded account that will send the payload (must have SS58Prefix = 42)
 		#[structopt(long)]
 		sender: AccountId,
 	},
 }
-
-/// All possible messages that may be delivered to generic Substrate chain.
-///
-/// Note this enum may be used in the context of both Source (as part of `encode-call`)
-/// and Target chain (as part of `encode-message/send-message`).
-#[derive(StructOpt, Debug)]
-pub enum Call {
-	/// Raw bytes for the message
-	Raw {
-		/// Raw, SCALE-encoded message
-		data: HexBytes,
-	},
-	/// Make an on-chain remark (comment).
-	Remark {
-		/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
-		#[structopt(long)]
-		remark_size: Option<ExplicitOrMaximal<usize>>,
-	},
-	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
-	Transfer {
-		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
-		#[structopt(long)]
-		recipient: AccountId,
-		/// Amount of target tokens to send in target chain base currency units.
-		#[structopt(long)]
-		amount: Balance,
-	},
-	// TODO [#853] Support multiple bridges.
-	/// A call to the specific Bridge Messages pallet to queue message to be sent over a bridge.
-	BridgeSendMessage {
-		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-		/// Raw SCALE-encoded Message Payload to submit to the messages pallet.
-		#[structopt(long)]
-		payload: HexBytes,
-		/// Declared delivery and dispatch fee in base source-chain currency units.
-		#[structopt(long)]
-		fee: Balance,
-	},
-}
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/mod.rs b/bridges/relays/bin-substrate/src/rialto_millau/mod.rs
index f039aadef54..52f1d5201cd 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/mod.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/mod.rs
@@ -28,7 +28,10 @@ pub type MillauClient = relay_substrate_client::Client<Millau>;
 /// Rialto node client.
 pub type RialtoClient = relay_substrate_client::Client<Rialto>;
 
-use crate::cli::{CliChain, ExplicitOrMaximal, HexBytes, Origins};
+use crate::cli::{
+	encode_call::{self, Call, CliEncodeCall, MILLAU_TO_RIALTO_INDEX, RIALTO_TO_MILLAU_INDEX},
+	CliChain, ExplicitOrMaximal, HexBytes, Origins,
+};
 use codec::{Decode, Encode};
 use frame_support::weights::{GetDispatchInfo, Weight};
 use pallet_bridge_dispatch::{CallOrigin, MessagePayload};
@@ -48,7 +51,7 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			source_sign,
 			target_sign,
 			lane,
-			message,
+			mut message,
 			dispatch_weight,
 			fee,
 			origin,
@@ -75,7 +78,9 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			let source_client = source.into_client::<Source>().await.map_err(format_err)?;
 			let source_sign = source_sign.into_keypair::<Source>().map_err(format_err)?;
 			let target_sign = target_sign.into_keypair::<Target>().map_err(format_err)?;
-			let target_call = Target::encode_call(message)?;
+
+			encode_call::preprocess_call::<Source, Target>(&mut message, MILLAU_TO_RIALTO_INDEX);
+			let target_call = Target::encode_call(&message).map_err(|e| e.to_string())?;
 
 			let payload = {
 				let target_call_weight = prepare_call_dispatch_weight(
@@ -154,7 +159,7 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			source_sign,
 			target_sign,
 			lane,
-			message,
+			mut message,
 			dispatch_weight,
 			fee,
 			origin,
@@ -181,7 +186,9 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			let source_client = source.into_client::<Source>().await.map_err(format_err)?;
 			let source_sign = source_sign.into_keypair::<Source>().map_err(format_err)?;
 			let target_sign = target_sign.into_keypair::<Target>().map_err(format_err)?;
-			let target_call = Target::encode_call(message)?;
+
+			encode_call::preprocess_call::<Source, Target>(&mut message, RIALTO_TO_MILLAU_INDEX);
+			let target_call = Target::encode_call(&message).map_err(|e| e.to_string())?;
 
 			let payload = {
 				let target_call_weight = prepare_call_dispatch_weight(
@@ -259,24 +266,6 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 	Ok(())
 }
 
-async fn run_encode_call(call: cli::EncodeCall) -> Result<(), String> {
-	match call {
-		cli::EncodeCall::Rialto { call } => {
-			type Source = Rialto;
-
-			let call = Source::encode_call(call)?;
-			println!("{:?}", HexBytes::encode(&call));
-		}
-		cli::EncodeCall::Millau { call } => {
-			type Source = Millau;
-
-			let call = Source::encode_call(call)?;
-			println!("{:?}", HexBytes::encode(&call));
-		}
-	}
-	Ok(())
-}
-
 async fn run_encode_message_payload(call: cli::EncodeMessagePayload) -> Result<(), String> {
 	match call {
 		cli::EncodeMessagePayload::RialtoToMillau { payload } => {
@@ -348,22 +337,6 @@ async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: En
 	Ok(decoded_response)
 }
 
-fn remark_payload(remark_size: Option<ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
-	match remark_size {
-		Some(ExplicitOrMaximal::Explicit(remark_size)) => vec![0; remark_size],
-		Some(ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
-		None => format!(
-			"Unix time: {}",
-			std::time::SystemTime::now()
-				.duration_since(std::time::SystemTime::UNIX_EPOCH)
-				.unwrap_or_default()
-				.as_secs(),
-		)
-		.as_bytes()
-		.to_vec(),
-	}
-}
-
 fn message_payload<SAccountId, TPublic, TSignature>(
 	spec_version: u32,
 	weight: Weight,
@@ -433,24 +406,41 @@ fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) ->
 	bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
 }
 
-fn compute_maximal_message_arguments_size(
-	maximal_source_extrinsic_size: u32,
-	maximal_target_extrinsic_size: u32,
-) -> u32 {
-	// assume that both signed extensions and other arguments fit 1KB
-	let service_tx_bytes_on_source_chain = 1024;
-	let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
-	let maximal_call_size =
-		bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
-	let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
-		maximal_source_extrinsic_size
-	} else {
-		maximal_call_size
-	};
+impl CliEncodeCall for Millau {
+	fn max_extrinsic_size() -> u32 {
+		bp_millau::max_extrinsic_size()
+	}
 
-	// bytes in Call encoding that are used to encode everything except arguments
-	let service_bytes = 1 + 1 + 4;
-	maximal_call_size - service_bytes
+	fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
+		Ok(match call {
+			Call::Raw { data } => Decode::decode(&mut &*data.0)?,
+			Call::Remark { remark_payload, .. } => {
+				millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload.0.clone()))
+			}
+			Call::Transfer { recipient, amount } => millau_runtime::Call::Balances(
+				millau_runtime::BalancesCall::transfer(recipient.raw_id(), amount.cast()),
+			),
+			Call::BridgeSendMessage {
+				lane,
+				payload,
+				fee,
+				bridge_instance_index,
+			} => match *bridge_instance_index {
+				encode_call::MILLAU_TO_RIALTO_INDEX => {
+					let payload = Decode::decode(&mut &*payload.0)?;
+					millau_runtime::Call::BridgeRialtoMessages(millau_runtime::MessagesCall::send_message(
+						lane.0,
+						payload,
+						fee.cast(),
+					))
+				}
+				_ => anyhow::bail!(
+					"Unsupported target bridge pallet with instance index: {}",
+					bridge_instance_index
+				),
+			},
+		})
+	}
 }
 
 impl CliChain for Millau {
@@ -467,58 +457,20 @@ impl CliChain for Millau {
 		bp_millau::max_extrinsic_weight()
 	}
 
-	fn encode_call(call: cli::Call) -> Result<Self::Call, String> {
-		let call = match call {
-			cli::Call::Raw { data } => {
-				Decode::decode(&mut &*data.0).map_err(|e| format!("Unable to decode message: {:#?}", e))?
-			}
-			cli::Call::Remark { remark_size } => {
-				millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload(
-					remark_size,
-					compute_maximal_message_arguments_size(
-						bp_rialto::max_extrinsic_size(),
-						bp_millau::max_extrinsic_size(),
-					),
-				)))
-			}
-			cli::Call::Transfer { mut recipient, amount } => {
-				recipient.enforce_chain::<Millau>();
-				let amount = amount.cast();
-				millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer(recipient.raw_id(), amount))
-			}
-			cli::Call::BridgeSendMessage { lane, payload, fee } => {
-				type Target = Rialto;
-
-				let payload = Target::encode_message(cli::MessagePayload::Raw { data: payload })?;
-				let lane = lane.into();
-				millau_runtime::Call::BridgeRialtoMessages(millau_runtime::MessagesCall::send_message(
-					lane,
-					payload,
-					fee.cast(),
-				))
-			}
-		};
-
-		log::info!(target: "bridge", "Generated Millau call: {:#?}", call);
-		log::info!(target: "bridge", "Weight of Millau call: {}", call.get_dispatch_info().weight);
-		log::info!(target: "bridge", "Encoded Millau call: {:?}", HexBytes::encode(&call));
-
-		Ok(call)
-	}
-
 	// TODO [#854|#843] support multiple bridges?
 	fn encode_message(message: cli::MessagePayload) -> Result<Self::MessagePayload, String> {
 		match message {
 			cli::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
 				.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
-			cli::MessagePayload::Call { call, mut sender } => {
+			cli::MessagePayload::Call { mut call, mut sender } => {
 				type Source = Millau;
 				type Target = Rialto;
 
 				sender.enforce_chain::<Source>();
 				let spec_version = Target::RUNTIME_VERSION.spec_version;
 				let origin = CallOrigin::SourceAccount(sender.raw_id());
-				let call = Target::encode_call(call)?;
+				encode_call::preprocess_call::<Source, Target>(&mut call, MILLAU_TO_RIALTO_INDEX);
+				let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
 				let weight = call.get_dispatch_info().weight;
 
 				Ok(message_payload(spec_version, weight, origin, &call))
@@ -527,6 +479,41 @@ impl CliChain for Millau {
 	}
 }
 
+impl CliEncodeCall for Rialto {
+	fn max_extrinsic_size() -> u32 {
+		bp_rialto::max_extrinsic_size()
+	}
+
+	fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
+		Ok(match call {
+			Call::Raw { data } => Decode::decode(&mut &*data.0)?,
+			Call::Remark { remark_payload, .. } => {
+				rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload.0.clone()))
+			}
+			Call::Transfer { recipient, amount } => {
+				rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient.raw_id(), amount.0))
+			}
+			Call::BridgeSendMessage {
+				lane,
+				payload,
+				fee,
+				bridge_instance_index,
+			} => match *bridge_instance_index {
+				encode_call::RIALTO_TO_MILLAU_INDEX => {
+					let payload = Decode::decode(&mut &*payload.0)?;
+					rialto_runtime::Call::BridgeMillauMessages(rialto_runtime::MessagesCall::send_message(
+						lane.0, payload, fee.0,
+					))
+				}
+				_ => anyhow::bail!(
+					"Unsupported target bridge pallet with instance index: {}",
+					bridge_instance_index
+				),
+			},
+		})
+	}
+}
+
 impl CliChain for Rialto {
 	const RUNTIME_VERSION: RuntimeVersion = rialto_runtime::VERSION;
 
@@ -541,57 +528,19 @@ impl CliChain for Rialto {
 		bp_rialto::max_extrinsic_weight()
 	}
 
-	fn encode_call(call: cli::Call) -> Result<Self::Call, String> {
-		let call = match call {
-			cli::Call::Raw { data } => {
-				Decode::decode(&mut &*data.0).map_err(|e| format!("Unable to decode message: {:#?}", e))?
-			}
-			cli::Call::Remark { remark_size } => {
-				rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload(
-					remark_size,
-					compute_maximal_message_arguments_size(
-						bp_millau::max_extrinsic_size(),
-						bp_rialto::max_extrinsic_size(),
-					),
-				)))
-			}
-			cli::Call::Transfer { mut recipient, amount } => {
-				type Source = Rialto;
-
-				recipient.enforce_chain::<Source>();
-				let amount = amount.0;
-				rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient.raw_id(), amount))
-			}
-			cli::Call::BridgeSendMessage { lane, payload, fee } => {
-				type Target = Millau;
-
-				let payload = Target::encode_message(cli::MessagePayload::Raw { data: payload })?;
-				let lane = lane.into();
-				rialto_runtime::Call::BridgeMillauMessages(rialto_runtime::MessagesCall::send_message(
-					lane, payload, fee.0,
-				))
-			}
-		};
-
-		log::info!(target: "bridge", "Generated Rialto call: {:#?}", call);
-		log::info!(target: "bridge", "Weight of Rialto call: {}", call.get_dispatch_info().weight);
-		log::info!(target: "bridge", "Encoded Rialto call: {:?}", HexBytes::encode(&call));
-
-		Ok(call)
-	}
-
 	fn encode_message(message: cli::MessagePayload) -> Result<Self::MessagePayload, String> {
 		match message {
 			cli::MessagePayload::Raw { data } => MessagePayload::decode(&mut &*data.0)
 				.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
-			cli::MessagePayload::Call { call, mut sender } => {
+			cli::MessagePayload::Call { mut call, mut sender } => {
 				type Source = Rialto;
 				type Target = Millau;
 
 				sender.enforce_chain::<Source>();
 				let spec_version = Target::RUNTIME_VERSION.spec_version;
 				let origin = CallOrigin::SourceAccount(sender.raw_id());
-				let call = Target::encode_call(call)?;
+				encode_call::preprocess_call::<Source, Target>(&mut call, RIALTO_TO_MILLAU_INDEX);
+				let call = Target::encode_call(&call).map_err(|e| e.to_string())?;
 				let weight = call.get_dispatch_info().weight;
 
 				Ok(message_payload(spec_version, weight, origin, &call))
@@ -614,10 +563,6 @@ impl CliChain for Westend {
 		0
 	}
 
-	fn encode_call(_: cli::Call) -> Result<Self::Call, String> {
-		Err("Calling into Westend is not supported yet.".into())
-	}
-
 	fn encode_message(_message: cli::MessagePayload) -> Result<Self::MessagePayload, String> {
 		Err("Sending messages from Westend is not yet supported.".into())
 	}
@@ -680,8 +625,10 @@ mod tests {
 	fn maximal_rialto_to_millau_message_arguments_size_is_computed_correctly() {
 		use rialto_runtime::millau_messages::Millau;
 
-		let maximal_remark_size =
-			compute_maximal_message_arguments_size(bp_rialto::max_extrinsic_size(), bp_millau::max_extrinsic_size());
+		let maximal_remark_size = encode_call::compute_maximal_message_arguments_size(
+			bp_rialto::max_extrinsic_size(),
+			bp_millau::max_extrinsic_size(),
+		);
 
 		let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
 		let payload = message_payload(
-- 
GitLab