referrerpolicy=no-referrer-when-downgrade

pallet_bridge_relayers/
stake_adapter.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//! Code that allows `NamedReservableCurrency` to be used as a `StakeAndSlash`
18//! mechanism of the relayers pallet.
19
20use bp_relayers::StakeAndSlash;
21use codec::Codec;
22use core::{fmt::Debug, marker::PhantomData};
23use frame_support::traits::{tokens::BalanceStatus, NamedReservableCurrency};
24use sp_runtime::{traits::Get, DispatchError, DispatchResult};
25
26/// `StakeAndSlash` that works with `NamedReservableCurrency` and uses named
27/// reservations.
28///
29/// **WARNING**: this implementation assumes that the relayers pallet is configured to
30/// use the [`bp_relayers::PayRewardFromAccount`] as its relayers payment scheme.
31pub struct StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>(
32	PhantomData<(AccountId, BlockNumber, Currency, ReserveId, Stake, Lease)>,
33);
34
35impl<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
36	StakeAndSlash<AccountId, BlockNumber, Currency::Balance>
37	for StakeAndSlashNamed<AccountId, BlockNumber, Currency, ReserveId, Stake, Lease>
38where
39	AccountId: Codec + Debug,
40	Currency: NamedReservableCurrency<AccountId>,
41	ReserveId: Get<Currency::ReserveIdentifier>,
42	Stake: Get<Currency::Balance>,
43	Lease: Get<BlockNumber>,
44{
45	type RequiredStake = Stake;
46	type RequiredRegistrationLease = Lease;
47
48	fn reserve(relayer: &AccountId, amount: Currency::Balance) -> DispatchResult {
49		Currency::reserve_named(&ReserveId::get(), relayer, amount)
50	}
51
52	fn unreserve(relayer: &AccountId, amount: Currency::Balance) -> Currency::Balance {
53		Currency::unreserve_named(&ReserveId::get(), relayer, amount)
54	}
55
56	fn repatriate_reserved(
57		relayer: &AccountId,
58		beneficiary: &AccountId,
59		amount: Currency::Balance,
60	) -> Result<Currency::Balance, DispatchError> {
61		Currency::repatriate_reserved_named(
62			&ReserveId::get(),
63			relayer,
64			&beneficiary,
65			amount,
66			BalanceStatus::Free,
67		)
68	}
69}
70
71#[cfg(test)]
72mod tests {
73	use super::*;
74	use crate::mock::*;
75	use bp_relayers::ExplicitOrAccountParams;
76
77	use frame_support::traits::fungible::Mutate;
78	use sp_runtime::traits::IdentifyAccount;
79
80	fn test_stake() -> ThisChainBalance {
81		Stake::get()
82	}
83
84	#[test]
85	fn reserve_works() {
86		run_test(|| {
87			assert!(TestStakeAndSlash::reserve(&1, test_stake()).is_err());
88			assert_eq!(Balances::free_balance(1), 0);
89			assert_eq!(Balances::reserved_balance(1), 0);
90
91			Balances::mint_into(&2, test_stake() - 1).unwrap();
92			assert!(TestStakeAndSlash::reserve(&2, test_stake()).is_err());
93			assert_eq!(Balances::free_balance(2), test_stake() - 1);
94			assert_eq!(Balances::reserved_balance(2), 0);
95
96			Balances::mint_into(&3, test_stake() * 2).unwrap();
97			assert_eq!(TestStakeAndSlash::reserve(&3, test_stake()), Ok(()));
98			assert_eq!(Balances::free_balance(3), test_stake());
99			assert_eq!(Balances::reserved_balance(3), test_stake());
100		})
101	}
102
103	#[test]
104	fn unreserve_works() {
105		run_test(|| {
106			assert_eq!(TestStakeAndSlash::unreserve(&1, test_stake()), test_stake());
107			assert_eq!(Balances::free_balance(1), 0);
108			assert_eq!(Balances::reserved_balance(1), 0);
109
110			Balances::mint_into(&2, test_stake() * 2).unwrap();
111			TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
112			assert_eq!(
113				TestStakeAndSlash::unreserve(&2, test_stake()),
114				test_stake() - test_stake() / 3
115			);
116			assert_eq!(Balances::free_balance(2), test_stake() * 2);
117			assert_eq!(Balances::reserved_balance(2), 0);
118
119			Balances::mint_into(&3, test_stake() * 2).unwrap();
120			TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
121			assert_eq!(TestStakeAndSlash::unreserve(&3, test_stake()), 0);
122			assert_eq!(Balances::free_balance(3), test_stake() * 2);
123			assert_eq!(Balances::reserved_balance(3), 0);
124		})
125	}
126
127	#[test]
128	fn repatriate_reserved_works() {
129		run_test(|| {
130			let beneficiary = test_reward_account_param();
131			let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
132
133			let mut expected_balance = ExistentialDeposit::get();
134			Balances::mint_into(&beneficiary_account, expected_balance).unwrap();
135
136			assert_eq!(
137				TestStakeAndSlash::repatriate_reserved(
138					&1,
139					&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
140					test_stake()
141				),
142				Ok(test_stake())
143			);
144			assert_eq!(Balances::free_balance(1), 0);
145			assert_eq!(Balances::reserved_balance(1), 0);
146			assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
147			assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
148
149			expected_balance += test_stake() / 3;
150			Balances::mint_into(&2, test_stake() * 2).unwrap();
151			TestStakeAndSlash::reserve(&2, test_stake() / 3).unwrap();
152			assert_eq!(
153				TestStakeAndSlash::repatriate_reserved(
154					&2,
155					&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
156					test_stake()
157				),
158				Ok(test_stake() - test_stake() / 3)
159			);
160			assert_eq!(Balances::free_balance(2), test_stake() * 2 - test_stake() / 3);
161			assert_eq!(Balances::reserved_balance(2), 0);
162			assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
163			assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
164
165			expected_balance += test_stake();
166			Balances::mint_into(&3, test_stake() * 2).unwrap();
167			TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
168			assert_eq!(
169				TestStakeAndSlash::repatriate_reserved(
170					&3,
171					&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
172					test_stake()
173				),
174				Ok(0)
175			);
176			assert_eq!(Balances::free_balance(3), test_stake());
177			assert_eq!(Balances::reserved_balance(3), 0);
178			assert_eq!(Balances::free_balance(beneficiary_account), expected_balance);
179			assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
180		})
181	}
182
183	#[test]
184	fn repatriate_reserved_doesnt_work_when_beneficiary_account_is_missing() {
185		run_test(|| {
186			let beneficiary = test_reward_account_param();
187			let beneficiary_account = TestPaymentProcedure::rewards_account(beneficiary);
188
189			Balances::mint_into(&3, test_stake() * 2).unwrap();
190			TestStakeAndSlash::reserve(&3, test_stake()).unwrap();
191			assert!(TestStakeAndSlash::repatriate_reserved(
192				&3,
193				&(ExplicitOrAccountParams::Params(beneficiary).into_account()),
194				test_stake()
195			)
196			.is_err());
197			assert_eq!(Balances::free_balance(3), test_stake());
198			assert_eq!(Balances::reserved_balance(3), test_stake());
199			assert_eq!(Balances::free_balance(beneficiary_account), 0);
200			assert_eq!(Balances::reserved_balance(beneficiary_account), 0);
201		});
202	}
203}