1extern crate alloc;
5
6use crate::reward::RewardPaymentError::{ChargeFeesFailure, XcmSendFailure};
7use bp_relayers::PaymentProcedure;
8use codec::DecodeWithMemTracking;
9use frame_support::{dispatch::GetDispatchInfo, PalletError};
10use scale_info::TypeInfo;
11use sp_runtime::{
12 codec::{Decode, Encode},
13 traits::Get,
14 DispatchError,
15};
16use sp_std::{fmt::Debug, marker::PhantomData};
17use xcm::{
18 opaque::latest::prelude::Xcm,
19 prelude::{ExecuteXcm, Junction::*, Location, SendXcm, *},
20};
21
22#[derive(Debug, Clone, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
25pub enum MessageId {
26 Inbound(u64),
28 Outbound(u64),
30}
31
32#[derive(Debug, Encode, PartialEq, DecodeWithMemTracking, Decode, TypeInfo, PalletError)]
33pub enum AddTipError {
34 NonceConsumed,
35 UnknownMessage,
36 AmountZero,
37}
38
39pub trait AddTip {
41 fn add_tip(nonce: u64, amount: u128) -> Result<(), AddTipError>;
43}
44
45#[derive(Debug, Encode, Decode)]
47pub enum RewardPaymentError {
48 XcmSendFailure,
50 ChargeFeesFailure,
52}
53
54impl From<RewardPaymentError> for DispatchError {
55 fn from(e: RewardPaymentError) -> DispatchError {
56 match e {
57 XcmSendFailure => DispatchError::Other("xcm send failure"),
58 ChargeFeesFailure => DispatchError::Other("charge fees error"),
59 }
60 }
61}
62
63pub struct PayAccountOnLocation<
66 Relayer,
67 RewardBalance,
68 EthereumNetwork,
69 AssetHubLocation,
70 InboundQueueLocation,
71 XcmSender,
72 XcmExecutor,
73 Call,
74>(
75 PhantomData<(
76 Relayer,
77 RewardBalance,
78 EthereumNetwork,
79 AssetHubLocation,
80 InboundQueueLocation,
81 XcmSender,
82 XcmExecutor,
83 Call,
84 )>,
85);
86
87impl<
88 Relayer,
89 RewardBalance,
90 EthereumNetwork,
91 AssetHubLocation,
92 InboundQueueLocation,
93 XcmSender,
94 XcmExecutor,
95 Call,
96 > PaymentProcedure<Relayer, (), RewardBalance>
97 for PayAccountOnLocation<
98 Relayer,
99 RewardBalance,
100 EthereumNetwork,
101 AssetHubLocation,
102 InboundQueueLocation,
103 XcmSender,
104 XcmExecutor,
105 Call,
106 >
107where
108 Relayer: Clone
109 + Debug
110 + Decode
111 + Encode
112 + Eq
113 + TypeInfo
114 + Into<sp_runtime::AccountId32>
115 + Into<Location>,
116 EthereumNetwork: Get<NetworkId>,
117 InboundQueueLocation: Get<InteriorLocation>,
118 AssetHubLocation: Get<Location>,
119 XcmSender: SendXcm,
120 RewardBalance: Into<u128> + Clone,
121 XcmExecutor: ExecuteXcm<Call>,
122 Call: Decode + GetDispatchInfo,
123{
124 type Error = DispatchError;
125 type Beneficiary = Location;
126
127 fn pay_reward(
128 relayer: &Relayer,
129 _: (),
130 reward: RewardBalance,
131 beneficiary: Self::Beneficiary,
132 ) -> Result<(), Self::Error> {
133 let ethereum_location = Location::new(2, [GlobalConsensus(EthereumNetwork::get())]);
134 let assets: Asset = (ethereum_location.clone(), reward.into()).into();
135
136 let xcm: Xcm<()> = alloc::vec![
137 UnpaidExecution { weight_limit: Unlimited, check_origin: None },
138 DescendOrigin(InboundQueueLocation::get().into()),
139 UniversalOrigin(GlobalConsensus(EthereumNetwork::get())),
140 ReserveAssetDeposited(assets.into()),
141 DepositAsset { assets: AllCounted(1).into(), beneficiary },
142 ]
143 .into();
144
145 let (ticket, fee) =
146 validate_send::<XcmSender>(AssetHubLocation::get(), xcm).map_err(|_| XcmSendFailure)?;
147 XcmExecutor::charge_fees(relayer.clone(), fee).map_err(|_| ChargeFeesFailure)?;
148 XcmSender::deliver(ticket).map_err(|_| XcmSendFailure)?;
149
150 Ok(())
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use frame_support::parameter_types;
158 use sp_runtime::AccountId32;
159
160 #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
161 pub struct MockRelayer(pub AccountId32);
162
163 impl From<MockRelayer> for AccountId32 {
164 fn from(m: MockRelayer) -> Self {
165 m.0
166 }
167 }
168
169 impl From<MockRelayer> for Location {
170 fn from(_m: MockRelayer) -> Self {
171 Location::new(1, Here)
173 }
174 }
175
176 pub enum BridgeReward {
177 Snowbridge,
178 }
179
180 parameter_types! {
181 pub AssetHubLocation: Location = Location::new(1,[Parachain(1000)]);
182 pub InboundQueueLocation: InteriorLocation = [PalletInstance(84)].into();
183 pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
184 pub const DefaultMyRewardKind: BridgeReward = BridgeReward::Snowbridge;
185 }
186
187 pub enum Weightless {}
188 impl PreparedMessage for Weightless {
189 fn weight_of(&self) -> Weight {
190 unreachable!();
191 }
192 }
193
194 pub struct MockXcmExecutor;
195 impl<C> ExecuteXcm<C> for MockXcmExecutor {
196 type Prepared = Weightless;
197 fn prepare(_: Xcm<C>, _: Weight) -> Result<Self::Prepared, InstructionError> {
198 Err(InstructionError { index: 0, error: XcmError::Unimplemented })
199 }
200 fn execute(
201 _: impl Into<Location>,
202 _: Self::Prepared,
203 _: &mut XcmHash,
204 _: Weight,
205 ) -> Outcome {
206 unreachable!()
207 }
208 fn charge_fees(_: impl Into<Location>, _: Assets) -> xcm::latest::Result {
209 Ok(())
210 }
211 }
212
213 #[derive(Debug, Decode, Default)]
214 pub struct MockCall;
215 impl GetDispatchInfo for MockCall {
216 fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo {
217 Default::default()
218 }
219 }
220
221 pub struct MockXcmSender;
222 impl SendXcm for MockXcmSender {
223 type Ticket = Xcm<()>;
224
225 fn validate(
226 dest: &mut Option<Location>,
227 xcm: &mut Option<Xcm<()>>,
228 ) -> SendResult<Self::Ticket> {
229 if let Some(location) = dest {
230 match location.unpack() {
231 (_, [Parachain(1001)]) => return Err(SendError::NotApplicable),
232 _ => Ok((xcm.clone().unwrap(), Assets::default())),
233 }
234 } else {
235 Ok((xcm.clone().unwrap(), Assets::default()))
236 }
237 }
238
239 fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
240 let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
241 Ok(hash)
242 }
243 }
244
245 #[test]
246 fn pay_reward_success() {
247 let relayer = MockRelayer(AccountId32::new([1u8; 32]));
248 let beneficiary = Location::new(1, Here);
249 let reward = 1_000u128;
250
251 type TestedPayAccountOnLocation = PayAccountOnLocation<
252 MockRelayer,
253 u128,
254 EthereumNetwork,
255 AssetHubLocation,
256 InboundQueueLocation,
257 MockXcmSender,
258 MockXcmExecutor,
259 MockCall,
260 >;
261
262 let result = TestedPayAccountOnLocation::pay_reward(&relayer, (), reward, beneficiary);
263
264 assert!(result.is_ok());
265 }
266
267 #[test]
268 fn pay_reward_fails_on_xcm_validate_xcm() {
269 struct FailingXcmValidator;
270 impl SendXcm for FailingXcmValidator {
271 type Ticket = ();
272
273 fn validate(
274 _dest: &mut Option<Location>,
275 _xcm: &mut Option<Xcm<()>>,
276 ) -> SendResult<Self::Ticket> {
277 Err(SendError::NotApplicable)
278 }
279
280 fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
281 let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
282 Ok(hash)
283 }
284 }
285
286 type FailingSenderPayAccount = PayAccountOnLocation<
287 MockRelayer,
288 u128,
289 EthereumNetwork,
290 AssetHubLocation,
291 InboundQueueLocation,
292 FailingXcmValidator,
293 MockXcmExecutor,
294 MockCall,
295 >;
296
297 let relayer = MockRelayer(AccountId32::new([1u8; 32]));
298 let reward = 1_000u128;
299 let beneficiary = Location::new(1, Here);
300 let result = FailingSenderPayAccount::pay_reward(&relayer, (), reward, beneficiary);
301
302 assert!(result.is_err());
303 let err_str = format!("{:?}", result.err().unwrap());
304 assert!(
305 err_str.contains("xcm send failure"),
306 "Expected xcm send failure error, got {:?}",
307 err_str
308 );
309 }
310
311 #[test]
312 fn pay_reward_fails_on_charge_fees() {
313 struct FailingXcmExecutor;
314 impl<C> ExecuteXcm<C> for FailingXcmExecutor {
315 type Prepared = Weightless;
316 fn prepare(_: Xcm<C>, _: Weight) -> Result<Self::Prepared, InstructionError> {
317 Err(InstructionError { index: 0, error: XcmError::Unimplemented })
318 }
319 fn execute(
320 _: impl Into<Location>,
321 _: Self::Prepared,
322 _: &mut XcmHash,
323 _: Weight,
324 ) -> Outcome {
325 unreachable!()
326 }
327 fn charge_fees(_: impl Into<Location>, _: Assets) -> xcm::latest::Result {
328 Err(crate::reward::SendError::Fees.into())
329 }
330 }
331
332 type FailingExecutorPayAccount = PayAccountOnLocation<
333 MockRelayer,
334 u128,
335 EthereumNetwork,
336 AssetHubLocation,
337 InboundQueueLocation,
338 MockXcmSender,
339 FailingXcmExecutor,
340 MockCall,
341 >;
342
343 let relayer = MockRelayer(AccountId32::new([3u8; 32]));
344 let beneficiary = Location::new(1, Here);
345 let reward = 500u128;
346 let result = FailingExecutorPayAccount::pay_reward(&relayer, (), reward, beneficiary);
347
348 assert!(result.is_err());
349 let err_str = format!("{:?}", result.err().unwrap());
350 assert!(
351 err_str.contains("charge fees error"),
352 "Expected 'charge fees error', got {:?}",
353 err_str
354 );
355 }
356
357 #[test]
358 fn pay_reward_fails_on_delivery() {
359 #[derive(Default)]
360 struct FailingDeliveryXcmSender;
361 impl SendXcm for FailingDeliveryXcmSender {
362 type Ticket = ();
363
364 fn validate(
365 _dest: &mut Option<Location>,
366 _xcm: &mut Option<Xcm<()>>,
367 ) -> SendResult<Self::Ticket> {
368 Ok(((), Assets::from(vec![])))
369 }
370
371 fn deliver(_xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
372 Err(SendError::NotApplicable)
373 }
374 }
375
376 type FailingDeliveryPayAccount = PayAccountOnLocation<
377 MockRelayer,
378 u128,
379 EthereumNetwork,
380 AssetHubLocation,
381 InboundQueueLocation,
382 FailingDeliveryXcmSender,
383 MockXcmExecutor,
384 MockCall,
385 >;
386
387 let relayer = MockRelayer(AccountId32::new([4u8; 32]));
388 let beneficiary = Location::new(1, Here);
389 let reward = 123u128;
390 let result = FailingDeliveryPayAccount::pay_reward(&relayer, (), reward, beneficiary);
391
392 assert!(result.is_err());
393 let err_str = format!("{:?}", result.err().unwrap());
394 assert!(
395 err_str.contains("xcm send failure"),
396 "Expected 'xcm delivery failure', got {:?}",
397 err_str
398 );
399 }
400}