bp_relayers/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Primitives of messages module.
18
19#![warn(missing_docs)]
20#![cfg_attr(not(feature = "std"), no_std)]
21
22pub use extension::{
23	BatchCallUnpacker, ExtensionCallData, ExtensionCallInfo, ExtensionConfig,
24	RuntimeWithUtilityPallet,
25};
26pub use registration::{ExplicitOrAccountParams, Registration, StakeAndSlash};
27
28use bp_runtime::{ChainId, StorageDoubleMapKeyProvider};
29use frame_support::{traits::tokens::Preservation, Blake2_128Concat, Identity};
30use scale_info::TypeInfo;
31use sp_runtime::{
32	codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen},
33	traits::AccountIdConversion,
34	TypeId,
35};
36use sp_std::{fmt::Debug, marker::PhantomData};
37
38mod extension;
39mod registration;
40
41/// The owner of the sovereign account that should pay the rewards.
42///
43/// Each of the 2 final points connected by a bridge owns a sovereign account at each end of the
44/// bridge. So here, at this end of the bridge there can be 2 sovereign accounts that pay rewards.
45#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
46pub enum RewardsAccountOwner {
47	/// The sovereign account of the final chain on this end of the bridge.
48	ThisChain,
49	/// The sovereign account of the final chain on the other end of the bridge.
50	BridgedChain,
51}
52
53/// Structure used to identify the account that pays a reward to the relayer.
54///
55/// A bridge connects 2 bridge ends. Each one is located on a separate relay chain. The bridge ends
56/// can be the final destinations of the bridge, or they can be intermediary points
57/// (e.g. a bridge hub) used to forward messages between pairs of parachains on the bridged relay
58/// chains. A pair of such parachains is connected using a bridge lane. Each of the 2 final
59/// destinations of a bridge lane must have a sovereign account at each end of the bridge and each
60/// of the sovereign accounts will pay rewards for different operations. So we need multiple
61/// parameters to identify the account that pays a reward to the relayer.
62#[derive(Copy, Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen)]
63pub struct RewardsAccountParams<LaneId> {
64	// **IMPORTANT NOTE**: the order of fields here matters - we are using
65	// `into_account_truncating` and lane id is already `32` byte, so if other fields are encoded
66	// after it, they're simply dropped. So lane id shall be the last field.
67	owner: RewardsAccountOwner,
68	bridged_chain_id: ChainId,
69	lane_id: LaneId,
70}
71
72impl<LaneId: Decode + Encode> RewardsAccountParams<LaneId> {
73	/// Create a new instance of `RewardsAccountParams`.
74	pub const fn new(
75		lane_id: LaneId,
76		bridged_chain_id: ChainId,
77		owner: RewardsAccountOwner,
78	) -> Self {
79		Self { lane_id, bridged_chain_id, owner }
80	}
81
82	/// Getter for `lane_id`.
83	pub const fn lane_id(&self) -> &LaneId {
84		&self.lane_id
85	}
86}
87
88impl<LaneId: Decode + Encode> TypeId for RewardsAccountParams<LaneId> {
89	const TYPE_ID: [u8; 4] = *b"brap";
90}
91
92/// Reward payment procedure.
93pub trait PaymentProcedure<Relayer, Reward> {
94	/// Error that may be returned by the procedure.
95	type Error: Debug;
96	/// Lane identifier type.
97	type LaneId: Decode + Encode;
98
99	/// Pay reward to the relayer from the account with provided params.
100	fn pay_reward(
101		relayer: &Relayer,
102		rewards_account_params: RewardsAccountParams<Self::LaneId>,
103		reward: Reward,
104	) -> Result<(), Self::Error>;
105}
106
107impl<Relayer, Reward> PaymentProcedure<Relayer, Reward> for () {
108	type Error = &'static str;
109	type LaneId = ();
110
111	fn pay_reward(
112		_: &Relayer,
113		_: RewardsAccountParams<Self::LaneId>,
114		_: Reward,
115	) -> Result<(), Self::Error> {
116		Ok(())
117	}
118}
119
120/// Reward payment procedure that does `balances::transfer` call from the account, derived from
121/// given params.
122pub struct PayRewardFromAccount<T, Relayer, LaneId>(PhantomData<(T, Relayer, LaneId)>);
123
124impl<T, Relayer, LaneId> PayRewardFromAccount<T, Relayer, LaneId>
125where
126	Relayer: Decode + Encode,
127	LaneId: Decode + Encode,
128{
129	/// Return account that pays rewards based on the provided parameters.
130	pub fn rewards_account(params: RewardsAccountParams<LaneId>) -> Relayer {
131		params.into_sub_account_truncating(b"rewards-account")
132	}
133}
134
135impl<T, Relayer, LaneId> PaymentProcedure<Relayer, T::Balance>
136	for PayRewardFromAccount<T, Relayer, LaneId>
137where
138	T: frame_support::traits::fungible::Mutate<Relayer>,
139	Relayer: Decode + Encode + Eq,
140	LaneId: Decode + Encode,
141{
142	type Error = sp_runtime::DispatchError;
143	type LaneId = LaneId;
144
145	fn pay_reward(
146		relayer: &Relayer,
147		rewards_account_params: RewardsAccountParams<Self::LaneId>,
148		reward: T::Balance,
149	) -> Result<(), Self::Error> {
150		T::transfer(
151			&Self::rewards_account(rewards_account_params),
152			relayer,
153			reward,
154			Preservation::Expendable,
155		)
156		.map(drop)
157	}
158}
159
160/// Can be used to access the runtime storage key within the `RelayerRewards` map of the relayers
161/// pallet.
162pub struct RelayerRewardsKeyProvider<AccountId, Reward, LaneId>(
163	PhantomData<(AccountId, Reward, LaneId)>,
164);
165
166impl<AccountId, Reward, LaneId> StorageDoubleMapKeyProvider
167	for RelayerRewardsKeyProvider<AccountId, Reward, LaneId>
168where
169	AccountId: 'static + Codec + EncodeLike + Send + Sync,
170	Reward: 'static + Codec + EncodeLike + Send + Sync,
171	LaneId: Codec + EncodeLike + Send + Sync,
172{
173	const MAP_NAME: &'static str = "RelayerRewards";
174
175	type Hasher1 = Blake2_128Concat;
176	type Key1 = AccountId;
177	type Hasher2 = Identity;
178	type Key2 = RewardsAccountParams<LaneId>;
179	type Value = Reward;
180}
181
182#[cfg(test)]
183mod tests {
184	use super::*;
185	use bp_messages::{HashedLaneId, LaneIdType, LegacyLaneId};
186	use sp_runtime::{app_crypto::Ss58Codec, testing::H256};
187
188	#[test]
189	fn different_lanes_are_using_different_accounts() {
190		assert_eq!(
191			PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account(
192				RewardsAccountParams::new(
193					HashedLaneId::try_new(1, 2).unwrap(),
194					*b"test",
195					RewardsAccountOwner::ThisChain
196				)
197			),
198			hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
199				.into(),
200		);
201
202		assert_eq!(
203			PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account(
204				RewardsAccountParams::new(
205					HashedLaneId::try_new(1, 3).unwrap(),
206					*b"test",
207					RewardsAccountOwner::ThisChain
208				)
209			),
210			hex_literal::hex!("627261700074657374a43e8951aa302c133beb5f85821a21645f07b487270ef3")
211				.into(),
212		);
213	}
214
215	#[test]
216	fn different_directions_are_using_different_accounts() {
217		assert_eq!(
218			PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account(
219				RewardsAccountParams::new(
220					HashedLaneId::try_new(1, 2).unwrap(),
221					*b"test",
222					RewardsAccountOwner::ThisChain
223				)
224			),
225			hex_literal::hex!("627261700074657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
226				.into(),
227		);
228
229		assert_eq!(
230			PayRewardFromAccount::<(), H256, HashedLaneId>::rewards_account(
231				RewardsAccountParams::new(
232					HashedLaneId::try_new(1, 2).unwrap(),
233					*b"test",
234					RewardsAccountOwner::BridgedChain
235				)
236			),
237			hex_literal::hex!("627261700174657374b1d3dccd8b3c3a012afe265f3e3c4432129b8aee50c9dc")
238				.into(),
239		);
240	}
241
242	#[test]
243	fn pay_reward_from_account_for_legacy_lane_id_works() {
244		let test_data = vec![
245			// Note: these accounts are used for integration tests within
246			// `bridges_rococo_westend.sh`
247			(
248				LegacyLaneId([0, 0, 0, 1]),
249				b"bhks",
250				RewardsAccountOwner::ThisChain,
251				(0_u16, "13E5fui97x6KTwNnSjaEKZ8s7kJNot5F3aUsy3jUtuoMyUec"),
252			),
253			(
254				LegacyLaneId([0, 0, 0, 1]),
255				b"bhks",
256				RewardsAccountOwner::BridgedChain,
257				(0_u16, "13E5fui9Ka9Vz4JbGN3xWjmwDNxnxF1N9Hhhbeu3VCqLChuj"),
258			),
259			(
260				LegacyLaneId([0, 0, 0, 1]),
261				b"bhpd",
262				RewardsAccountOwner::ThisChain,
263				(2_u16, "EoQBtnwtXqnSnr9cgBEJpKU7NjeC9EnR4D1VjgcvHz9ZYmS"),
264			),
265			(
266				LegacyLaneId([0, 0, 0, 1]),
267				b"bhpd",
268				RewardsAccountOwner::BridgedChain,
269				(2_u16, "EoQBtnx69txxumxSJexVzxYD1Q4LWAuWmRq8LrBWb27nhYN"),
270			),
271			// Note: these accounts are used for integration tests within
272			// `bridges_polkadot_kusama.sh` from fellows.
273			(
274				LegacyLaneId([0, 0, 0, 2]),
275				b"bhwd",
276				RewardsAccountOwner::ThisChain,
277				(4_u16, "SNihsskf7bFhnHH9HJFMjWD3FJ96ESdAQTFZUAtXudRQbaH"),
278			),
279			(
280				LegacyLaneId([0, 0, 0, 2]),
281				b"bhwd",
282				RewardsAccountOwner::BridgedChain,
283				(4_u16, "SNihsskrjeSDuD5xumyYv9H8sxZEbNkG7g5C5LT8CfPdaSE"),
284			),
285			(
286				LegacyLaneId([0, 0, 0, 2]),
287				b"bhro",
288				RewardsAccountOwner::ThisChain,
289				(4_u16, "SNihsskf7bF2vWogkC6uFoiqPhd3dUX6TGzYZ1ocJdo3xHp"),
290			),
291			(
292				LegacyLaneId([0, 0, 0, 2]),
293				b"bhro",
294				RewardsAccountOwner::BridgedChain,
295				(4_u16, "SNihsskrjeRZ3ScWNfq6SSnw2N3BzQeCAVpBABNCbfmHENB"),
296			),
297		];
298
299		for (lane_id, bridged_chain_id, owner, (expected_ss58, expected_account)) in test_data {
300			assert_eq!(
301				expected_account,
302				sp_runtime::AccountId32::new(PayRewardFromAccount::<
303					[u8; 32],
304					[u8; 32],
305					LegacyLaneId,
306				>::rewards_account(RewardsAccountParams::new(
307					lane_id,
308					*bridged_chain_id,
309					owner
310				)))
311				.to_ss58check_with_version(expected_ss58.into())
312			);
313		}
314	}
315}