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 #[allow(dead_code)]
177 pub enum BridgeReward {
178 Snowbridge,
179 }
180
181 parameter_types! {
182 pub AssetHubLocation: Location = Location::new(1,[Parachain(1000)]);
183 pub InboundQueueLocation: InteriorLocation = [PalletInstance(84)].into();
184 pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
185 pub const DefaultMyRewardKind: BridgeReward = BridgeReward::Snowbridge;
186 }
187
188 pub enum Weightless {}
189 impl PreparedMessage for Weightless {
190 fn weight_of(&self) -> Weight {
191 unreachable!();
192 }
193 }
194
195 pub struct MockXcmExecutor;
196 impl<C> ExecuteXcm<C> for MockXcmExecutor {
197 type Prepared = Weightless;
198 fn prepare(_: Xcm<C>, _: Weight) -> Result<Self::Prepared, InstructionError> {
199 Err(InstructionError { index: 0, error: XcmError::Unimplemented })
200 }
201 fn execute(
202 _: impl Into<Location>,
203 _: Self::Prepared,
204 _: &mut XcmHash,
205 _: Weight,
206 ) -> Outcome {
207 unreachable!()
208 }
209 fn charge_fees(_: impl Into<Location>, _: Assets) -> xcm::latest::Result {
210 Ok(())
211 }
212 }
213
214 #[derive(Debug, Decode, Default)]
215 pub struct MockCall;
216 impl GetDispatchInfo for MockCall {
217 fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo {
218 Default::default()
219 }
220 }
221
222 pub struct MockXcmSender;
223 impl SendXcm for MockXcmSender {
224 type Ticket = Xcm<()>;
225
226 fn validate(
227 dest: &mut Option<Location>,
228 xcm: &mut Option<Xcm<()>>,
229 ) -> SendResult<Self::Ticket> {
230 if let Some(location) = dest {
231 match location.unpack() {
232 (_, [Parachain(1001)]) => return Err(SendError::NotApplicable),
233 _ => Ok((xcm.clone().unwrap(), Assets::default())),
234 }
235 } else {
236 Ok((xcm.clone().unwrap(), Assets::default()))
237 }
238 }
239
240 fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
241 let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
242 Ok(hash)
243 }
244 }
245
246 #[test]
247 fn pay_reward_success() {
248 let relayer = MockRelayer(AccountId32::new([1u8; 32]));
249 let beneficiary = Location::new(1, Here);
250 let reward = 1_000u128;
251
252 type TestedPayAccountOnLocation = PayAccountOnLocation<
253 MockRelayer,
254 u128,
255 EthereumNetwork,
256 AssetHubLocation,
257 InboundQueueLocation,
258 MockXcmSender,
259 MockXcmExecutor,
260 MockCall,
261 >;
262
263 let result = TestedPayAccountOnLocation::pay_reward(&relayer, (), reward, beneficiary);
264
265 assert!(result.is_ok());
266 }
267
268 #[test]
269 fn pay_reward_fails_on_xcm_validate_xcm() {
270 struct FailingXcmValidator;
271 impl SendXcm for FailingXcmValidator {
272 type Ticket = ();
273
274 fn validate(
275 _dest: &mut Option<Location>,
276 _xcm: &mut Option<Xcm<()>>,
277 ) -> SendResult<Self::Ticket> {
278 Err(SendError::NotApplicable)
279 }
280
281 fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
282 let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
283 Ok(hash)
284 }
285 }
286
287 type FailingSenderPayAccount = PayAccountOnLocation<
288 MockRelayer,
289 u128,
290 EthereumNetwork,
291 AssetHubLocation,
292 InboundQueueLocation,
293 FailingXcmValidator,
294 MockXcmExecutor,
295 MockCall,
296 >;
297
298 let relayer = MockRelayer(AccountId32::new([1u8; 32]));
299 let reward = 1_000u128;
300 let beneficiary = Location::new(1, Here);
301 let result = FailingSenderPayAccount::pay_reward(&relayer, (), reward, beneficiary);
302
303 assert!(result.is_err());
304 let err_str = format!("{:?}", result.err().unwrap());
305 assert!(
306 err_str.contains("xcm send failure"),
307 "Expected xcm send failure error, got {:?}",
308 err_str
309 );
310 }
311
312 #[test]
313 fn pay_reward_fails_on_charge_fees() {
314 struct FailingXcmExecutor;
315 impl<C> ExecuteXcm<C> for FailingXcmExecutor {
316 type Prepared = Weightless;
317 fn prepare(_: Xcm<C>, _: Weight) -> Result<Self::Prepared, InstructionError> {
318 Err(InstructionError { index: 0, error: XcmError::Unimplemented })
319 }
320 fn execute(
321 _: impl Into<Location>,
322 _: Self::Prepared,
323 _: &mut XcmHash,
324 _: Weight,
325 ) -> Outcome {
326 unreachable!()
327 }
328 fn charge_fees(_: impl Into<Location>, _: Assets) -> xcm::latest::Result {
329 Err(crate::reward::SendError::Fees.into())
330 }
331 }
332
333 type FailingExecutorPayAccount = PayAccountOnLocation<
334 MockRelayer,
335 u128,
336 EthereumNetwork,
337 AssetHubLocation,
338 InboundQueueLocation,
339 MockXcmSender,
340 FailingXcmExecutor,
341 MockCall,
342 >;
343
344 let relayer = MockRelayer(AccountId32::new([3u8; 32]));
345 let beneficiary = Location::new(1, Here);
346 let reward = 500u128;
347 let result = FailingExecutorPayAccount::pay_reward(&relayer, (), reward, beneficiary);
348
349 assert!(result.is_err());
350 let err_str = format!("{:?}", result.err().unwrap());
351 assert!(
352 err_str.contains("charge fees error"),
353 "Expected 'charge fees error', got {:?}",
354 err_str
355 );
356 }
357
358 #[test]
359 fn pay_reward_fails_on_delivery() {
360 #[derive(Default)]
361 struct FailingDeliveryXcmSender;
362 impl SendXcm for FailingDeliveryXcmSender {
363 type Ticket = ();
364
365 fn validate(
366 _dest: &mut Option<Location>,
367 _xcm: &mut Option<Xcm<()>>,
368 ) -> SendResult<Self::Ticket> {
369 Ok(((), Assets::from(vec![])))
370 }
371
372 fn deliver(_xcm: Self::Ticket) -> core::result::Result<XcmHash, SendError> {
373 Err(SendError::NotApplicable)
374 }
375 }
376
377 type FailingDeliveryPayAccount = PayAccountOnLocation<
378 MockRelayer,
379 u128,
380 EthereumNetwork,
381 AssetHubLocation,
382 InboundQueueLocation,
383 FailingDeliveryXcmSender,
384 MockXcmExecutor,
385 MockCall,
386 >;
387
388 let relayer = MockRelayer(AccountId32::new([4u8; 32]));
389 let beneficiary = Location::new(1, Here);
390 let reward = 123u128;
391 let result = FailingDeliveryPayAccount::pay_reward(&relayer, (), reward, beneficiary);
392
393 assert!(result.is_err());
394 let err_str = format!("{:?}", result.err().unwrap());
395 assert!(
396 err_str.contains("xcm send failure"),
397 "Expected 'xcm delivery failure', got {:?}",
398 err_str
399 );
400 }
401}