Skip to content

Commit f4fe373

Browse files
authored
Merge pull request #1328 from galacticcouncil/fix/dca-termiante
fix: terminate dca with linear search
2 parents 1a62dec + 1a23b64 commit f4fe373

File tree

8 files changed

+129
-32
lines changed

8 files changed

+129
-32
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pallets/dca/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = 'pallet-dca'
3-
version = "1.11.0"
3+
version = "1.12.0"
44
description = 'A pallet to manage DCA scheduling'
55
authors = ['GalacticCouncil']
66
edition = '2021'

pallets/dca/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -649,11 +649,11 @@ pub mod pallet {
649649
|maybe_schedule_ids| -> DispatchResult {
650650
let schedule_ids = maybe_schedule_ids.as_mut().ok_or(Error::<T>::ScheduleNotFound)?;
651651

652-
let index = schedule_ids
653-
.binary_search(&schedule_id)
654-
.map_err(|_| Error::<T>::ScheduleNotFound)?;
655-
656-
schedule_ids.remove(index);
652+
let idx = schedule_ids
653+
.iter()
654+
.position(|x| *x == schedule_id)
655+
.ok_or(Error::<T>::ScheduleNotFound)?;
656+
schedule_ids.remove(idx);
657657

658658
if schedule_ids.is_empty() {
659659
*maybe_schedule_ids = None;

pallets/dca/src/tests/terminate.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,95 @@ fn terminate_should_work_with_no_blocknumber_when_just_scheduled() {
384384
});
385385
}
386386

387+
#[test]
388+
fn terminate_should_not_depend_on_schedule_ids_per_block_ordering() {
389+
ExtBuilder::default()
390+
.with_endowed_accounts(vec![(ALICE, HDX, 10000 * ONE), (BOB, HDX, 10000 * ONE)])
391+
.build()
392+
.execute_with(|| {
393+
let (block_600, schedule_id_0, schedule_id_1) = arrange_unsorted_schedule_ids_per_block_600();
394+
395+
let reserved_before = Currencies::reserved_balance_named(&NamedReserveId::get(), HDX, &ALICE);
396+
assert!(reserved_before > 0);
397+
398+
assert_ok!(DCA::terminate(RuntimeOrigin::signed(ALICE), schedule_id_1, None));
399+
400+
assert_that_schedule_has_been_removed_from_storages!(ALICE, schedule_id_1);
401+
assert_eq!(DCA::schedule_ids_per_block(block_600).to_vec(), vec![schedule_id_0]);
402+
assert_eq!(
403+
Currencies::reserved_balance_named(&NamedReserveId::get(), HDX, &ALICE),
404+
0
405+
);
406+
});
407+
}
408+
409+
fn arrange_unsorted_schedule_ids_per_block_600() -> (u64, ScheduleId, ScheduleId) {
410+
// Builds `ScheduleIdsPerBlock[600] == [1, 0]` via normal flow.
411+
let block_502 = 502u64;
412+
let block_600 = 600u64;
413+
let schedule_id_0: ScheduleId = 0;
414+
let schedule_id_1: ScheduleId = 1;
415+
416+
set_block_number(500);
417+
418+
let schedule_replans_into_block_600 = ScheduleBuilder::new()
419+
.with_owner(BOB)
420+
.with_period(98)
421+
.with_order(Order::Sell {
422+
asset_in: HDX,
423+
asset_out: BTC,
424+
amount_in: ONE,
425+
min_amount_out: 0,
426+
route: create_bounded_vec(vec![Trade {
427+
pool: PoolType::Omnipool,
428+
asset_in: HDX,
429+
asset_out: BTC,
430+
}]),
431+
})
432+
.build();
433+
assert_ok!(DCA::schedule(
434+
RuntimeOrigin::signed(BOB),
435+
schedule_replans_into_block_600,
436+
None
437+
));
438+
assert!(DCA::schedules(schedule_id_0).is_some());
439+
440+
let schedule_planned_for_block_600 = ScheduleBuilder::new()
441+
.with_total_amount(0)
442+
.with_order(Order::Sell {
443+
asset_in: HDX,
444+
asset_out: BTC,
445+
amount_in: ONE,
446+
min_amount_out: 0,
447+
route: create_bounded_vec(vec![Trade {
448+
pool: PoolType::Omnipool,
449+
asset_in: HDX,
450+
asset_out: BTC,
451+
}]),
452+
})
453+
.build();
454+
assert_ok!(DCA::schedule(
455+
RuntimeOrigin::signed(ALICE),
456+
schedule_planned_for_block_600,
457+
Some(block_600)
458+
));
459+
assert!(DCA::schedules(schedule_id_1).is_some());
460+
461+
set_block_number(block_502);
462+
463+
assert!(DCA::schedules(schedule_id_1).is_some());
464+
assert_eq!(DCA::schedule_execution_block(schedule_id_1), Some(block_600));
465+
assert_eq!(
466+
DCA::schedule_ids_per_block(block_600).to_vec(),
467+
vec![schedule_id_1, schedule_id_0]
468+
);
469+
assert!(DCA::schedule_ids_per_block(block_600)
470+
.iter()
471+
.any(|id| *id == schedule_id_1));
472+
473+
(block_600, schedule_id_0, schedule_id_1)
474+
}
475+
387476
pub fn set_block_number(to: u64) {
388477
System::set_block_number(to);
389478
DCA::on_initialize(to);

runtime/hydradx/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "hydradx-runtime"
3-
version = "382.0.0"
3+
version = "383.0.0"
44
authors = ["GalacticCouncil"]
55
edition = "2021"
66
license = "Apache 2.0"

runtime/hydradx/src/benchmarking/dca.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -519,17 +519,25 @@ runtime_benchmarks! {
519519

520520
let amount_sell = 200 * ONE;
521521
let schedule1 = schedule_fake(caller.clone(), HDX, DAI, amount_sell);
522-
let schedule_id : ScheduleId = 0;
523522

524523
set_period(99);
525-
let execution_block = 100u32;
526-
assert_ok!(DCA::schedule(RawOrigin::Signed(caller).into(), schedule1, Option::Some(execution_block)));
527-
ScheduleExtraGas::<Runtime>::insert(schedule_id, 1_000_000);
528524

525+
let execution_block = 105u32;
526+
527+
// Fill block with MaxSchedulesPerBlock schedules to test worst case for linear search
528+
for _ in 0..MaxSchedulesPerBlock::get() {
529+
assert_ok!(DCA::schedule(RawOrigin::Signed(caller.clone()).into(), schedule1.clone(), Option::Some(execution_block)));
530+
}
531+
532+
// Terminate the last schedule which is at the last index in ScheduleIdsPerBlock
533+
// This is worst case for linear search - must iterate through all elements
534+
let schedule_id: ScheduleId = MaxSchedulesPerBlock::get() - 1;
535+
ScheduleExtraGas::<Runtime>::insert(schedule_id, 1_000_000);
529536

530537
}: _(RawOrigin::Root, schedule_id, None)
531538
verify {
532539
assert!(<Schedules<Runtime>>::get::<ScheduleId>(schedule_id).is_none());
540+
assert_eq!((MaxSchedulesPerBlock::get() - 1) as usize, <ScheduleIdsPerBlock<Runtime>>::get::<BlockNumber>(execution_block).len());
533541
}
534542

535543
unlock_reserves {

runtime/hydradx/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
122122
spec_name: create_runtime_str!("hydradx"),
123123
impl_name: create_runtime_str!("hydradx"),
124124
authoring_version: 1,
125-
spec_version: 382,
125+
spec_version: 383,
126126
impl_version: 0,
127127
apis: RUNTIME_API_VERSIONS,
128128
transaction_version: 1,

runtime/hydradx/src/weights/pallet_dca.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
//! Autogenerated weights for `pallet_dca`
2020
//!
2121
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 43.0.0
22-
//! DATE: 2026-01-10, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
22+
//! DATE: 2026-01-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
2323
//! WORST CASE MAP SIZE: `1000000`
2424
//! HOSTNAME: `bench-bot`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
2525
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
@@ -82,8 +82,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
8282
// Proof Size summary in bytes:
8383
// Measured: `19910`
8484
// Estimated: `31230`
85-
// Minimum execution time: 241_462_000 picoseconds.
86-
Weight::from_parts(244_164_000, 31230)
85+
// Minimum execution time: 244_500_000 picoseconds.
86+
Weight::from_parts(245_810_000, 31230)
8787
.saturating_add(T::DbWeight::get().reads(19_u64))
8888
.saturating_add(T::DbWeight::get().writes(9_u64))
8989
}
@@ -137,8 +137,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
137137
// Proof Size summary in bytes:
138138
// Measured: `26447`
139139
// Estimated: `31230`
140-
// Minimum execution time: 451_876_000 picoseconds.
141-
Weight::from_parts(455_359_000, 31230)
140+
// Minimum execution time: 452_314_000 picoseconds.
141+
Weight::from_parts(456_558_000, 31230)
142142
.saturating_add(T::DbWeight::get().reads(41_u64))
143143
.saturating_add(T::DbWeight::get().writes(12_u64))
144144
}
@@ -164,8 +164,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
164164
// Proof Size summary in bytes:
165165
// Measured: `19658`
166166
// Estimated: `31230`
167-
// Minimum execution time: 245_109_000 picoseconds.
168-
Weight::from_parts(247_849_000, 31230)
167+
// Minimum execution time: 246_503_000 picoseconds.
168+
Weight::from_parts(248_412_000, 31230)
169169
.saturating_add(T::DbWeight::get().reads(19_u64))
170170
.saturating_add(T::DbWeight::get().writes(9_u64))
171171
}
@@ -219,8 +219,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
219219
// Proof Size summary in bytes:
220220
// Measured: `26707`
221221
// Estimated: `31230`
222-
// Minimum execution time: 453_637_000 picoseconds.
223-
Weight::from_parts(456_962_000, 31230)
222+
// Minimum execution time: 455_030_000 picoseconds.
223+
Weight::from_parts(457_587_000, 31230)
224224
.saturating_add(T::DbWeight::get().reads(41_u64))
225225
.saturating_add(T::DbWeight::get().writes(12_u64))
226226
}
@@ -230,8 +230,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
230230
// Proof Size summary in bytes:
231231
// Measured: `1113`
232232
// Estimated: `3510`
233-
// Minimum execution time: 20_010_000 picoseconds.
234-
Weight::from_parts(20_454_000, 3510)
233+
// Minimum execution time: 20_129_000 picoseconds.
234+
Weight::from_parts(20_578_000, 3510)
235235
.saturating_add(T::DbWeight::get().reads(1_u64))
236236
}
237237
/// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:2 w:0)
@@ -268,8 +268,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
268268
// Proof Size summary in bytes:
269269
// Measured: `19305`
270270
// Estimated: `28710`
271-
// Minimum execution time: 236_756_000 picoseconds.
272-
Weight::from_parts(238_471_000, 28710)
271+
// Minimum execution time: 235_459_000 picoseconds.
272+
Weight::from_parts(237_584_000, 28710)
273273
.saturating_add(T::DbWeight::get().reads(24_u64))
274274
.saturating_add(T::DbWeight::get().writes(9_u64))
275275
}
@@ -293,10 +293,10 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
293293
/// Proof: `DCA::ScheduleOwnership` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`)
294294
fn terminate() -> Weight {
295295
// Proof Size summary in bytes:
296-
// Measured: `2642`
296+
// Measured: `4072`
297297
// Estimated: `4714`
298-
// Minimum execution time: 95_747_000 picoseconds.
299-
Weight::from_parts(96_689_000, 4714)
298+
// Minimum execution time: 101_721_000 picoseconds.
299+
Weight::from_parts(102_910_000, 4714)
300300
.saturating_add(T::DbWeight::get().reads(6_u64))
301301
.saturating_add(T::DbWeight::get().writes(9_u64))
302302
}
@@ -310,8 +310,8 @@ impl<T: frame_system::Config> pallet_dca::WeightInfo for HydraWeight<T> {
310310
// Proof Size summary in bytes:
311311
// Measured: `2144`
312312
// Estimated: `4714`
313-
// Minimum execution time: 62_338_000 picoseconds.
314-
Weight::from_parts(62_941_000, 4714)
313+
// Minimum execution time: 61_941_000 picoseconds.
314+
Weight::from_parts(62_696_000, 4714)
315315
.saturating_add(T::DbWeight::get().reads(3_u64))
316316
.saturating_add(T::DbWeight::get().writes(2_u64))
317317
}

0 commit comments

Comments
 (0)