1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi 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
// Tanssi 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 Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use {
18
    crate::{
19
        assert_expected_events,
20
        common::xcm::{
21
            core_buyer_common::*,
22
            mocknets::{DanceboxRococoPara as Dancebox, RococoRelay as Rococo, RococoRelayPallet},
23
            *,
24
        },
25
    },
26
    polkadot_runtime_parachains::assigner_on_demand as parachains_assigner_on_demand,
27
    staging_xcm::latest::{MaybeErrorCode, Response},
28
    tp_traits::ParaId,
29
    xcm_emulator::Chain,
30
};
31

            
32
const PARATHREAD_ID: u32 = 3333;
33
const ROCOCO_ED: u128 = rococo_runtime_constants::currency::EXISTENTIAL_DEPOSIT;
34
const BUY_EXECUTION_COST: u128 = dancebox_runtime::xcm_config::XCM_BUY_EXECUTION_COST_ROCOCO;
35
// Difference between BUY_EXECUTION_COST and the actual cost that depends on the weight of the XCM
36
// message, gets refunded on successful execution of core buying extrinsic.
37
const BUY_EXECUTION_REFUND: u128 = 3334777;
38
// Difference between BUY_EXECUTION_COST and the actual cost that depends on the weight of the XCM
39
// message, gets refunded on un-successful execution of core buying extrinsic.
40
const BUY_EXECUTION_REFUND_ON_FAILURE: u128 = 1001467;
41

            
42
#[test]
43
1
fn constants() {
44
1
    // If these constants change, some tests may break
45
1
    assert_eq!(ROCOCO_ED, 100_000_000 / 3);
46
1
    assert_eq!(BUY_EXECUTION_COST, 70_000_000 + 126_666_399);
47
1
}
48

            
49
#[test]
50
1
fn xcm_core_buyer_zero_balance() {
51
1
    let parathread_tank_in_relay = get_parathread_tank_relay_address();
52
1
    let balance_before = 0;
53
1

            
54
1
    // Invariant: if balance_before < BUY_EXECUTION_COST, then balance_after == balance_before
55
1
    let query_id = do_test(balance_before, None, false);
56
1

            
57
1
    // Receive XCM message in Relay Chain
58
1
    Rococo::execute_with(|| {
59
1
        type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
60
1
        let balance_after =
61
1
            <Rococo as RococoRelayPallet>::System::account(parathread_tank_in_relay.clone())
62
1
                .data
63
1
                .free;
64
        assert_expected_events!(
65
            Rococo,
66
            vec![
67
                RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: false, .. }) => {},
68
            ]
69
        );
70
1
        assert_relay_order_event_not_emitted();
71
1
        assert_eq!(balance_before, balance_after);
72
1
    });
73
1

            
74
1
    Dancebox::execute_with(|| {
75
1
        assert_xcm_notification_event_not_emitted();
76
1
        assert_query_response_not_received(ParaId::from(PARATHREAD_ID), query_id);
77
1
    });
78
1
}
79

            
80
#[test]
81
1
fn xcm_core_buyer_only_enough_balance_for_buy_execution() {
82
1
    let parathread_tank_in_relay = get_parathread_tank_relay_address();
83
1
    let balance_before = BUY_EXECUTION_COST;
84
1

            
85
1
    // Invariant: if balance_before >= BUY_EXECUTION_COST then
86
1
    // balance_after <= (balance_before + BUY_EXECUTION_REFUND - BUY_EXECUTION_COST)
87
1
    // In this case the balance_after is 0 because BUY_EXECUTION_REFUND < ROCOCO_ED,
88
1
    // so the account gets the refund but it is immediatelly killed.
89
1
    let query_id = do_test(balance_before, None, false);
90
1

            
91
1
    // Receive XCM message in Relay Chain
92
1
    Rococo::execute_with(|| {
93
1
        type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
94
1
        let balance_after =
95
1
            <Rococo as RococoRelayPallet>::System::account(parathread_tank_in_relay.clone())
96
1
                .data
97
1
                .free;
98
        assert_expected_events!(
99
            Rococo,
100
            vec![
101
                RuntimeEvent::Balances(
102
                    pallet_balances::Event::Burned {
103
                        who,
104
                        amount: BUY_EXECUTION_COST,
105
                    }
106
                ) => {
107
                    who: *who == parathread_tank_in_relay,
108
                },
109
                RuntimeEvent::System(
110
                    frame_system::Event::KilledAccount {
111
                        account,
112
                    }
113
                ) => {
114
                    account: *account == parathread_tank_in_relay,
115
                },
116
                RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: false, .. }) => {},
117
            ]
118
        );
119
1
        assert_relay_order_event_not_emitted();
120
1
        assert_eq!(balance_after, 0);
121
1
    });
122
1

            
123
1
    Dancebox::execute_with(|| {
124
        type RuntimeEvent = <Dancebox as Chain>::RuntimeEvent;
125
        assert_expected_events!(
126
            Dancebox,
127
            vec![
128
                RuntimeEvent::XcmCoreBuyer(
129
                    pallet_xcm_core_buyer::Event::ReceivedBuyCoreXCMResult {
130
                        para_id,
131
                        response,
132
                    }
133
                ) => {
134
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
135
                    response: *response != Response::DispatchResult(MaybeErrorCode::Success),
136
                },
137
            ]
138
        );
139
1
        assert_query_response_failure(ParaId::from(PARATHREAD_ID), query_id);
140
1
    });
141
1
}
142

            
143
#[test]
144
1
fn xcm_core_buyer_enough_balance_except_for_existential_deposit() {
145
1
    let parathread_tank_in_relay = get_parathread_tank_relay_address();
146
1
    // Core price must be greater than existential deposit
147
1
    let spot_price = ROCOCO_ED + 1;
148
1
    set_on_demand_base_fee(spot_price);
149
1
    let spot_price2 = spot_price;
150
1
    let balance_before = BUY_EXECUTION_COST + spot_price;
151
1

            
152
1
    let query_id = do_test(balance_before, None, false);
153
1

            
154
1
    // Receive XCM message in Relay Chain
155
1
    Rococo::execute_with(|| {
156
1
        type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
157
1

            
158
1
        let balance_after =
159
1
            <Rococo as RococoRelayPallet>::System::account(parathread_tank_in_relay.clone())
160
1
                .data
161
1
                .free;
162
        assert_expected_events!(
163
            Rococo,
164
            vec![
165
                RuntimeEvent::Balances(
166
                    pallet_balances::Event::Burned {
167
                        who,
168
                        amount: BUY_EXECUTION_COST,
169
                    }
170
                ) => {
171
                    who: *who == parathread_tank_in_relay,
172
                },
173
                RuntimeEvent::Balances(
174
                    pallet_balances::Event::Withdraw {
175
                        who,
176
                        amount,
177
                    }
178
                ) => {
179
                    who: *who == parathread_tank_in_relay,
180
                    amount: *amount == spot_price,
181
                },
182
                RuntimeEvent::System(
183
                    frame_system::Event::KilledAccount {
184
                        account,
185
                    }
186
                ) => {
187
                    account: *account == parathread_tank_in_relay,
188
                },
189
                RuntimeEvent::OnDemandAssignmentProvider(
190
                    parachains_assigner_on_demand::Event::OnDemandOrderPlaced {
191
                        para_id,
192
                        spot_price,
193
                        ordered_by,
194
                    }
195
                ) => {
196
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
197
                    spot_price: *spot_price == spot_price2,
198
                    ordered_by: *ordered_by == parathread_tank_in_relay,
199
                },
200
                // TODO: this now emits "success: false" even though the on demand order was placed, will
201
                // that break pallet_xcm_core_buyer?
202
                RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: false, .. }) => {},
203
            ]
204
        );
205
1
        assert_eq!(balance_after, 0);
206
1
    });
207
1

            
208
1
    Dancebox::execute_with(|| {
209
        type RuntimeEvent = <Dancebox as Chain>::RuntimeEvent;
210
        assert_expected_events!(
211
            Dancebox,
212
            vec![
213
                RuntimeEvent::XcmCoreBuyer(
214
                    pallet_xcm_core_buyer::Event::ReceivedBuyCoreXCMResult {
215
                        para_id,
216
                        response,
217
                    }
218
                ) => {
219
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
220
                    response: *response == Response::DispatchResult(MaybeErrorCode::Success),
221
                },
222
            ]
223
        );
224
1
        assert_query_response_success(ParaId::from(PARATHREAD_ID), query_id);
225
1
    });
226
1
}
227

            
228
#[test]
229
1
fn xcm_core_buyer_enough_balance() {
230
1
    let parathread_tank_in_relay = get_parathread_tank_relay_address();
231
1
    let spot_price = get_on_demand_base_fee();
232
1
    let spot_price2 = spot_price;
233
1
    let balance_before = ROCOCO_ED + BUY_EXECUTION_COST + spot_price + 1;
234
1

            
235
1
    let query_id = do_test(balance_before, None, false);
236
1

            
237
1
    // Receive XCM message in Relay Chain
238
1
    Rococo::execute_with(|| {
239
1
        type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
240
1

            
241
1
        let balance_after =
242
1
            <Rococo as RococoRelayPallet>::System::account(parathread_tank_in_relay.clone())
243
1
                .data
244
1
                .free;
245
        assert_expected_events!(
246
            Rococo,
247
            vec![
248
                RuntimeEvent::Balances(
249
                    pallet_balances::Event::Burned {
250
                        who,
251
                        amount: BUY_EXECUTION_COST,
252
                    }
253
                ) => {
254
                    who: *who == parathread_tank_in_relay,
255
                },
256
                RuntimeEvent::Balances(
257
                    pallet_balances::Event::Withdraw {
258
                        who,
259
                        amount,
260
                    }
261
                ) => {
262
                    who: *who == parathread_tank_in_relay,
263
                    amount: *amount == spot_price,
264
                },
265
                RuntimeEvent::OnDemandAssignmentProvider(
266
                    parachains_assigner_on_demand::Event::OnDemandOrderPlaced {
267
                        para_id,
268
                        spot_price,
269
                        ordered_by,
270
                    }
271
                ) => {
272
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
273
                    spot_price: *spot_price == spot_price2,
274
                    ordered_by: *ordered_by == parathread_tank_in_relay,
275
                },
276
                RuntimeEvent::Balances(
277
                    pallet_balances::Event::Minted {
278
                        who,
279
                        amount: BUY_EXECUTION_REFUND,
280
                    }
281
                ) => {
282
                    who: *who == parathread_tank_in_relay,
283
                },
284
                RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {},
285
            ]
286
        );
287
1
        assert_eq!(balance_after, ROCOCO_ED + 1 + BUY_EXECUTION_REFUND);
288
1
    });
289
1

            
290
1
    // Receive notification on dancebox
291
1
    Dancebox::execute_with(|| {
292
        type RuntimeEvent = <Dancebox as Chain>::RuntimeEvent;
293
        assert_expected_events!(
294
            Dancebox,
295
            vec![
296
                RuntimeEvent::XcmCoreBuyer(
297
                    pallet_xcm_core_buyer::Event::ReceivedBuyCoreXCMResult {
298
                        para_id,
299
                        response,
300
                    }
301
                ) => {
302
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
303
                    response: *response == Response::DispatchResult(MaybeErrorCode::Success),
304
                },
305
            ]
306
        );
307

            
308
1
        assert_query_response_success(ParaId::from(PARATHREAD_ID), query_id);
309
1
    });
310
1
}
311

            
312
#[test]
313
1
fn xcm_core_buyer_core_too_expensive() {
314
1
    let parathread_tank_in_relay = get_parathread_tank_relay_address();
315
1
    let balance_before = ROCOCO_ED + BUY_EXECUTION_COST + 1;
316
1
    set_on_demand_base_fee(balance_before * 2);
317
1

            
318
1
    let query_id = do_test(balance_before, None, false);
319
1

            
320
1
    // Receive XCM message in Relay Chain
321
1
    Rococo::execute_with(|| {
322
1
        type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
323
1

            
324
1
        let balance_after =
325
1
            <Rococo as RococoRelayPallet>::System::account(parathread_tank_in_relay.clone())
326
1
                .data
327
1
                .free;
328
        assert_expected_events!(
329
            Rococo,
330
            vec![
331
                RuntimeEvent::Balances(
332
                    pallet_balances::Event::Burned {
333
                        who,
334
                        amount: BUY_EXECUTION_COST,
335
                    }
336
                ) => {
337
                    who: *who == parathread_tank_in_relay,
338
                },
339
                RuntimeEvent::Balances(
340
                    pallet_balances::Event::Minted {
341
                        who,
342
                        amount: BUY_EXECUTION_REFUND_ON_FAILURE,
343
                    }
344
                ) => {
345
                    who: *who == parathread_tank_in_relay,
346
                },
347
                RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {},
348
            ]
349
        );
350
1
        assert_relay_order_event_not_emitted();
351
1
        assert_eq!(
352
1
            balance_after,
353
1
            balance_before + BUY_EXECUTION_REFUND_ON_FAILURE - BUY_EXECUTION_COST
354
1
        );
355
1
    });
356
1

            
357
1
    // Receive notification on dancebox
358
1
    Dancebox::execute_with(|| {
359
        type RuntimeEvent = <Dancebox as Chain>::RuntimeEvent;
360
        assert_expected_events!(
361
            Dancebox,
362
            vec![
363
                RuntimeEvent::XcmCoreBuyer(
364
                    pallet_xcm_core_buyer::Event::ReceivedBuyCoreXCMResult {
365
                        para_id,
366
                        response,
367
                    }
368
                ) => {
369
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
370
                    response: *response != Response::DispatchResult(MaybeErrorCode::Success),
371
                },
372
            ]
373
        );
374

            
375
1
        assert_query_response_failure(ParaId::from(PARATHREAD_ID), query_id);
376
1
    });
377
1
}
378

            
379
#[test]
380
1
fn xcm_core_buyer_set_max_core_price() {
381
1
    let parathread_tank_in_relay = get_parathread_tank_relay_address();
382
1
    let spot_price = get_on_demand_base_fee();
383
1
    let balance_before = ROCOCO_ED + BUY_EXECUTION_COST + spot_price + 1;
384
1
    // Set max core price lower than spot_price, will result in no core bought even though the
385
1
    // account has enough balance
386
1
    let max_core_price = spot_price / 2;
387
1

            
388
1
    Dancebox::execute_with(|| {});
389
1

            
390
1
    let query_id = do_test(balance_before, Some(max_core_price), false);
391
1

            
392
1
    // Receive XCM message in Relay Chain
393
1
    Rococo::execute_with(|| {
394
1
        type RuntimeEvent = <Rococo as Chain>::RuntimeEvent;
395
1

            
396
1
        let balance_after =
397
1
            <Rococo as RococoRelayPallet>::System::account(parathread_tank_in_relay.clone())
398
1
                .data
399
1
                .free;
400
        assert_expected_events!(
401
            Rococo,
402
            vec![
403
                RuntimeEvent::Balances(
404
                    pallet_balances::Event::Burned {
405
                        who,
406
                        amount: BUY_EXECUTION_COST,
407
                    }
408
                ) => {
409
                    who: *who == parathread_tank_in_relay,
410
                },
411
                RuntimeEvent::Balances(
412
                    pallet_balances::Event::Minted {
413
                        who,
414
                        amount: BUY_EXECUTION_REFUND_ON_FAILURE,
415
                    }
416
                ) => {
417
                    who: *who == parathread_tank_in_relay,
418
                },
419
                RuntimeEvent::MessageQueue(pallet_message_queue::Event::Processed { success: true, .. }) => {},
420
            ]
421
        );
422
1
        assert_relay_order_event_not_emitted();
423
1
        assert_eq!(
424
1
            balance_after,
425
1
            ROCOCO_ED + 1 + BUY_EXECUTION_REFUND_ON_FAILURE + spot_price
426
1
        );
427
1
    });
428
1

            
429
1
    Dancebox::execute_with(|| {
430
        type RuntimeEvent = <Dancebox as Chain>::RuntimeEvent;
431
        assert_expected_events!(
432
            Dancebox,
433
            vec![
434
                RuntimeEvent::XcmCoreBuyer(
435
                    pallet_xcm_core_buyer::Event::ReceivedBuyCoreXCMResult {
436
                        para_id,
437
                        response,
438
                    }
439
                ) => {
440
                    para_id: *para_id == ParaId::from(PARATHREAD_ID),
441
                    response: *response != Response::DispatchResult(MaybeErrorCode::Success),
442
                },
443
            ]
444
        );
445

            
446
1
        assert_query_response_failure(ParaId::from(PARATHREAD_ID), query_id);
447
1
    });
448
1
}