diff --git a/Cargo.lock b/Cargo.lock index 0182723b6..826e90f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4919,7 +4919,7 @@ dependencies = [ [[package]] name = "hydradx-runtime" -version = "279.0.0" +version = "280.0.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", @@ -8010,7 +8010,7 @@ dependencies = [ [[package]] name = "pallet-dca" -version = "1.7.0" +version = "1.8.0" dependencies = [ "cumulus-pallet-parachain-system", "cumulus-primitives-core", @@ -12281,7 +12281,7 @@ dependencies = [ [[package]] name = "runtime-integration-tests" -version = "1.28.0" +version = "1.29.0" dependencies = [ "cumulus-pallet-aura-ext", "cumulus-pallet-parachain-system", diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 03c7e6317..6616b8ae9 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "runtime-integration-tests" -version = "1.28.0" +version = "1.29.0" description = "Integration tests" authors = ["GalacticCouncil"] edition = "2021" diff --git a/integration-tests/src/dca.rs b/integration-tests/src/dca.rs index cd697c83f..31119cc88 100644 --- a/integration-tests/src/dca.rs +++ b/integration-tests/src/dca.rs @@ -4026,6 +4026,38 @@ mod with_onchain_route { } } +#[test] +fn terminate_should_work_for_freshly_created_dca() { + TestNet::reset(); + Hydra::execute_with(|| { + //Arrange + init_omnipool_with_oracle_for_block_10(); + + let block_id = 11; + set_relaychain_block_number(block_id); + + let budget = 1000 * UNITS; + let schedule1 = schedule_fake_with_buy_order(PoolType::Omnipool, HDX, DAI, 100 * UNITS, budget); + + assert_ok!(DCA::schedule( + RuntimeOrigin::signed(ALICE.into()), + schedule1.clone(), + None + )); + + let schedule_id = 0; + let schedule = DCA::schedules(schedule_id); + assert!(schedule.is_some()); + + //Act + assert_ok!(DCA::terminate(RuntimeOrigin::signed(ALICE.into()), schedule_id, None)); + + //Assert + let schedule = DCA::schedules(schedule_id); + assert!(schedule.is_none()); + }); +} + fn create_xyk_pool_with_amounts(asset_a: u32, amount_a: u128, asset_b: u32, amount_b: u128) { assert_ok!(Currencies::update_balance( hydradx_runtime::RuntimeOrigin::root(), diff --git a/pallets/dca/Cargo.toml b/pallets/dca/Cargo.toml index c762feca6..d2afc9a45 100644 --- a/pallets/dca/Cargo.toml +++ b/pallets/dca/Cargo.toml @@ -1,6 +1,6 @@ [package] name = 'pallet-dca' -version = "1.7.0" +version = "1.8.0" description = 'A pallet to manage DCA scheduling' authors = ['GalacticCouncil'] edition = '2021' diff --git a/pallets/dca/src/lib.rs b/pallets/dca/src/lib.rs index 4d2db338a..2f726cd07 100644 --- a/pallets/dca/src/lib.rs +++ b/pallets/dca/src/lib.rs @@ -428,6 +428,12 @@ pub mod pallet { #[pallet::getter(fn retries_on_error)] pub type RetriesOnError = StorageMap<_, Blake2_128Concat, ScheduleId, u8, ValueQuery>; + /// Keep tracking the blocknumber when the schedule is planned to be executed + #[pallet::storage] + #[pallet::getter(fn schedule_execution_block)] + pub type ScheduleExecutionBlock = + StorageMap<_, Blake2_128Concat, ScheduleId, BlockNumberFor, OptionQuery>; + /// Keep tracking of the schedule ids to be executed in the block #[pallet::storage] #[pallet::getter(fn schedule_ids_per_block)] @@ -588,7 +594,9 @@ pub mod pallet { Self::try_unreserve_all(schedule_id, &schedule); - let next_execution_block = next_execution_block.ok_or(Error::::ScheduleNotFound)?; + let next_execution_block = next_execution_block + .or(Self::schedule_execution_block(schedule_id)) + .ok_or(Error::::ScheduleNotFound)?; //Remove schedule id from next execution block ScheduleIdsPerBlock::::try_mutate_exists( @@ -1060,6 +1068,8 @@ impl Pallet { Ok(()) })?; + ScheduleExecutionBlock::::insert(schedule_id, next_free_block); + Self::deposit_event(Event::ExecutionPlanned { id: schedule_id, who: who.clone(), @@ -1202,6 +1212,7 @@ impl Pallet { ScheduleOwnership::::remove(owner, schedule_id); RemainingAmounts::::remove(schedule_id); RetriesOnError::::remove(schedule_id); + ScheduleExecutionBlock::::remove(schedule_id); } } diff --git a/pallets/dca/src/tests/mod.rs b/pallets/dca/src/tests/mod.rs index 9336d1007..0af8a00c6 100644 --- a/pallets/dca/src/tests/mod.rs +++ b/pallets/dca/src/tests/mod.rs @@ -123,6 +123,7 @@ macro_rules! assert_that_schedule_has_been_removed_from_storages { assert!(DCA::schedules($schedule_id).is_none()); assert!(DCA::owner_of($owner, $schedule_id).is_none()); assert!(DCA::remaining_amounts($schedule_id).is_none()); + assert!(DCA::schedule_execution_block($schedule_id).is_none()); assert_eq!(DCA::retries_on_error($schedule_id), 0); }; } diff --git a/pallets/dca/src/tests/terminate.rs b/pallets/dca/src/tests/terminate.rs index de29a0a97..603a7f44a 100644 --- a/pallets/dca/src/tests/terminate.rs +++ b/pallets/dca/src/tests/terminate.rs @@ -17,6 +17,7 @@ use crate::tests::mock::*; use crate::tests::*; use crate::{assert_scheduled_ids, assert_that_schedule_has_been_removed_from_storages}; use crate::{Error, Event}; +use frame_support::traits::Hooks; use frame_support::{assert_noop, assert_ok}; use orml_traits::NamedMultiReservableCurrency; use pretty_assertions::assert_eq; @@ -299,6 +300,91 @@ fn terminate_should_fail_when_with_nonexisting_schedule() { }); } -pub fn set_block_number(n: u64) { - System::set_block_number(n); +#[test] +fn terminate_should_work_when_no_block_specified() { + ExtBuilder::default() + .with_endowed_accounts(vec![(ALICE, HDX, 10000 * ONE)]) + .build() + .execute_with(|| { + //Arrange + set_block_number(500); + let schedule = ScheduleBuilder::new().with_period(300).build(); + let schedule_id = 0; + assert_ok!(DCA::schedule(RuntimeOrigin::signed(ALICE), schedule, Option::Some(600))); + set_block_number(600); + + //Act + assert_ok!(DCA::terminate(RuntimeOrigin::root(), schedule_id, None)); + + //Assert + assert_that_schedule_has_been_removed_from_storages!(ALICE, schedule_id); + + expect_events(vec![Event::Terminated { + id: 0, + who: ALICE, + error: Error::::ManuallyTerminated.into(), + } + .into()]); + }); +} + +#[test] +fn terminate_should_work_when_no_block_specified_and_schedule_eeceuted_multiple_times() { + ExtBuilder::default() + .with_endowed_accounts(vec![(ALICE, HDX, 10000 * ONE)]) + .build() + .execute_with(|| { + //Arrange + set_block_number(500); + let schedule = ScheduleBuilder::new().with_period(300).build(); + let schedule_id = 0; + assert_ok!(DCA::schedule(RuntimeOrigin::signed(ALICE), schedule, Option::Some(600))); + set_block_number(600); + set_block_number(900); + + //Act + assert_ok!(DCA::terminate(RuntimeOrigin::root(), schedule_id, None)); + + //Assert + assert_that_schedule_has_been_removed_from_storages!(ALICE, schedule_id); + + expect_events(vec![Event::Terminated { + id: 0, + who: ALICE, + error: Error::::ManuallyTerminated.into(), + } + .into()]); + }); +} + +#[test] +fn terminate_should_work_with_no_blocknumber_when_just_scheduled() { + ExtBuilder::default() + .with_endowed_accounts(vec![(ALICE, HDX, 10000 * ONE)]) + .build() + .execute_with(|| { + //Arrange + set_block_number(500); + let schedule = ScheduleBuilder::new().with_period(300).build(); + let schedule_id = 0; + assert_ok!(DCA::schedule(RuntimeOrigin::signed(ALICE), schedule, Option::Some(600))); + + //Act + assert_ok!(DCA::terminate(RuntimeOrigin::root(), schedule_id, None)); + + //Assert + assert_that_schedule_has_been_removed_from_storages!(ALICE, schedule_id); + + expect_events(vec![Event::Terminated { + id: 0, + who: ALICE, + error: Error::::ManuallyTerminated.into(), + } + .into()]); + }); +} + +pub fn set_block_number(to: u64) { + System::set_block_number(to); + DCA::on_initialize(to); } diff --git a/runtime/hydradx/Cargo.toml b/runtime/hydradx/Cargo.toml index 627ee0a8f..a337b772e 100644 --- a/runtime/hydradx/Cargo.toml +++ b/runtime/hydradx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hydradx-runtime" -version = "279.0.0" +version = "280.0.0" authors = ["GalacticCouncil"] edition = "2021" license = "Apache 2.0" diff --git a/runtime/hydradx/src/benchmarking/dca.rs b/runtime/hydradx/src/benchmarking/dca.rs index f60d82774..6d22e55d1 100644 --- a/runtime/hydradx/src/benchmarking/dca.rs +++ b/runtime/hydradx/src/benchmarking/dca.rs @@ -489,7 +489,7 @@ runtime_benchmarks! { let execution_block = 100u32; assert_ok!(DCA::schedule(RawOrigin::Signed(caller).into(), schedule1, Option::Some(execution_block))); - }: _(RawOrigin::Root, schedule_id, Some(105)) + }: _(RawOrigin::Root, schedule_id, None) verify { assert!(>::get::(schedule_id).is_none()); } diff --git a/runtime/hydradx/src/lib.rs b/runtime/hydradx/src/lib.rs index 1ba07409d..7649170f9 100644 --- a/runtime/hydradx/src/lib.rs +++ b/runtime/hydradx/src/lib.rs @@ -113,7 +113,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("hydradx"), impl_name: create_runtime_str!("hydradx"), authoring_version: 1, - spec_version: 279, + spec_version: 280, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 1, diff --git a/runtime/hydradx/src/weights/pallet_dca.rs b/runtime/hydradx/src/weights/pallet_dca.rs index 27dbb0dc0..c10a93f7d 100644 --- a/runtime/hydradx/src/weights/pallet_dca.rs +++ b/runtime/hydradx/src/weights/pallet_dca.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for `pallet_dca` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2025-01-07, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2025-01-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `bench-bot`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` @@ -75,14 +75,16 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `Broadcast::OverflowCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `DCA::RetriesOnError` (r:0 w:1) /// Proof: `DCA::RetriesOnError` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `DCA::ScheduleExecutionBlock` (r:0 w:1) + /// Proof: `DCA::ScheduleExecutionBlock` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) fn on_initialize_with_buy_trade() -> Weight { // Proof Size summary in bytes: - // Measured: `54994` + // Measured: `54961` // Estimated: `31902` - // Minimum execution time: 221_533_000 picoseconds. - Weight::from_parts(225_800_000, 31902) + // Minimum execution time: 223_251_000 picoseconds. + Weight::from_parts(226_990_000, 31902) .saturating_add(T::DbWeight::get().reads(20_u64)) - .saturating_add(T::DbWeight::get().writes(9_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) } /// Storage: `DCA::ScheduleIdsPerBlock` (r:12 w:2) /// Proof: `DCA::ScheduleIdsPerBlock` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) @@ -126,14 +128,16 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `Broadcast::OverflowCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `DCA::RetriesOnError` (r:0 w:1) /// Proof: `DCA::RetriesOnError` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `DCA::ScheduleExecutionBlock` (r:0 w:1) + /// Proof: `DCA::ScheduleExecutionBlock` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) fn on_initialize_with_buy_trade_with_insufficient_fee_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `61068` + // Measured: `61036` // Estimated: `31902` - // Minimum execution time: 390_717_000 picoseconds. - Weight::from_parts(394_718_000, 31902) + // Minimum execution time: 389_247_000 picoseconds. + Weight::from_parts(393_383_000, 31902) .saturating_add(T::DbWeight::get().reads(40_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `DCA::ScheduleIdsPerBlock` (r:12 w:2) /// Proof: `DCA::ScheduleIdsPerBlock` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) @@ -153,14 +157,16 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `Broadcast::OverflowCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `DCA::RetriesOnError` (r:0 w:1) /// Proof: `DCA::RetriesOnError` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `DCA::ScheduleExecutionBlock` (r:0 w:1) + /// Proof: `DCA::ScheduleExecutionBlock` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) fn on_initialize_with_sell_trade() -> Weight { // Proof Size summary in bytes: - // Measured: `54782` + // Measured: `54749` // Estimated: `31902` - // Minimum execution time: 225_387_000 picoseconds. - Weight::from_parts(229_273_000, 31902) + // Minimum execution time: 224_459_000 picoseconds. + Weight::from_parts(231_067_000, 31902) .saturating_add(T::DbWeight::get().reads(20_u64)) - .saturating_add(T::DbWeight::get().writes(9_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) } /// Storage: `DCA::ScheduleIdsPerBlock` (r:12 w:2) /// Proof: `DCA::ScheduleIdsPerBlock` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) @@ -204,14 +210,16 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `Broadcast::OverflowCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `DCA::RetriesOnError` (r:0 w:1) /// Proof: `DCA::RetriesOnError` (`max_values`: None, `max_size`: Some(21), added: 2496, mode: `MaxEncodedLen`) + /// Storage: `DCA::ScheduleExecutionBlock` (r:0 w:1) + /// Proof: `DCA::ScheduleExecutionBlock` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) fn on_initialize_with_sell_trade_with_insufficient_fee_asset() -> Weight { // Proof Size summary in bytes: - // Measured: `61944` + // Measured: `61912` // Estimated: `31902` - // Minimum execution time: 388_951_000 picoseconds. - Weight::from_parts(395_681_000, 31902) + // Minimum execution time: 389_528_000 picoseconds. + Weight::from_parts(394_784_000, 31902) .saturating_add(T::DbWeight::get().reads(40_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `DCA::ScheduleIdsPerBlock` (r:1 w:0) /// Proof: `DCA::ScheduleIdsPerBlock` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) @@ -219,8 +227,8 @@ impl pallet_dca::WeightInfo for HydraWeight { // Proof Size summary in bytes: // Measured: `1113` // Estimated: `3566` - // Minimum execution time: 14_913_000 picoseconds. - Weight::from_parts(15_284_000, 3566) + // Minimum execution time: 15_221_000 picoseconds. + Weight::from_parts(15_513_000, 3566) .saturating_add(T::DbWeight::get().reads(1_u64)) } /// Storage: `MultiTransactionPayment::AcceptedCurrencies` (r:2 w:0) @@ -247,16 +255,18 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `DCA::Schedules` (`max_values`: None, `max_size`: Some(191), added: 2666, mode: `MaxEncodedLen`) /// Storage: `DCA::ScheduleOwnership` (r:0 w:1) /// Proof: `DCA::ScheduleOwnership` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) + /// Storage: `DCA::ScheduleExecutionBlock` (r:0 w:1) + /// Proof: `DCA::ScheduleExecutionBlock` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) /// Storage: `DCA::RemainingAmounts` (r:0 w:1) /// Proof: `DCA::RemainingAmounts` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) fn schedule() -> Weight { // Proof Size summary in bytes: - // Measured: `53335` + // Measured: `53368` // Estimated: `29326` - // Minimum execution time: 198_317_000 picoseconds. - Weight::from_parts(200_375_000, 29326) + // Minimum execution time: 197_976_000 picoseconds. + Weight::from_parts(201_142_000, 29326) .saturating_add(T::DbWeight::get().reads(23_u64)) - .saturating_add(T::DbWeight::get().writes(8_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) } /// Storage: `DCA::Schedules` (r:1 w:1) /// Proof: `DCA::Schedules` (`max_values`: None, `max_size`: Some(191), added: 2666, mode: `MaxEncodedLen`) @@ -266,6 +276,8 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `Balances::Reserves` (`max_values`: None, `max_size`: Some(1249), added: 3724, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `DCA::ScheduleExecutionBlock` (r:1 w:1) + /// Proof: `DCA::ScheduleExecutionBlock` (`max_values`: None, `max_size`: Some(24), added: 2499, mode: `MaxEncodedLen`) /// Storage: `DCA::ScheduleIdsPerBlock` (r:1 w:1) /// Proof: `DCA::ScheduleIdsPerBlock` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `DCA::RetriesOnError` (r:0 w:1) @@ -274,11 +286,11 @@ impl pallet_dca::WeightInfo for HydraWeight { /// Proof: `DCA::ScheduleOwnership` (`max_values`: None, `max_size`: Some(60), added: 2535, mode: `MaxEncodedLen`) fn terminate() -> Weight { // Proof Size summary in bytes: - // Measured: `2530` + // Measured: `2575` // Estimated: `4714` - // Minimum execution time: 72_071_000 picoseconds. - Weight::from_parts(73_000_000, 4714) - .saturating_add(T::DbWeight::get().reads(5_u64)) - .saturating_add(T::DbWeight::get().writes(7_u64)) + // Minimum execution time: 77_105_000 picoseconds. + Weight::from_parts(78_361_000, 4714) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) } } \ No newline at end of file