Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "runtime-integration-tests"
version = "1.81.1"
version = "1.82.0"
description = "Integration tests"
authors = ["GalacticCouncil"]
edition = "2021"
Expand Down
Binary file added integration-tests/dca-snapshot/SNAPSHOT_12309734
Binary file not shown.
67 changes: 66 additions & 1 deletion integration-tests/src/dca.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4860,7 +4860,16 @@ mod aave_atoken {
use super::*;
use hydradx_runtime::DCA;

const PATH_TO_SNAPSHOT: &str = "dca-snapshot/SNAPSHOT";
// Snapshot at block 12309734, produced with:
//
// ./target/release/scraper save-storage --slim \
// --uri wss://hydration.dotters.network \
// --at 0x5bfd245dd5612800f2291cef550a5e3f76b569e6de4d52d1c6cb122e674d838e \
// --pallet Omnipool Stableswap AssetRegistry EVM DynamicFees EmaOracle \
// MultiTransactionPayment Tokens Balances EVMAccounts Ethereum \
// EVMChainId HSM Router System Aura Timestamp DCA \
// --path integration-tests/dca-snapshot
const PATH_TO_SNAPSHOT: &str = "dca-snapshot/SNAPSHOT_12309734";

//Ignored as snapshot too big
//To verify locally, download snapshot with command `./target/release/scraper save-storage --uri wss://paseo-rpc.play.hydration.cloud --at 0x3db005212a4ae320a2808c6813880b583dacbf7df60b0314420e88f4f2dfe989`
Expand All @@ -4885,6 +4894,62 @@ mod aave_atoken {
assert!(schedule.is_some());
});
}

use frame_support::traits::OnInitialize;

#[test]
fn dca_should_succeed_after_retry_when_insufficient_balance_error_due_to_off_by_one_error() {
TestNet::reset();

hydra_live_ext(PATH_TO_SNAPSHOT).execute_with(|| {
//Arrange
assert_eq!(hydradx_runtime::System::block_number(), 12309734);
let schedule_id = 30972;
assert!(DCA::schedules(schedule_id).is_some());

//Act 1: first attempt fails with InsufficientBalance and is retried
DCA::on_initialize(12309735);
assert_trade_failed_with_omnipool_insufficient_balance(schedule_id);
assert_eq!(
DCA::retries_on_error(schedule_id),
1,
"first attempt should have been retried"
);
let retry_block =
DCA::schedule_execution_block(schedule_id).expect("schedule should be replanned to a retry block");

//Act 2: simulate elapsed time so aave liquidity index drifts (rounding boundary moves)
let now = hydradx_runtime::Timestamp::get();
hydradx_runtime::Timestamp::set_timestamp(now + 240_000);

//Act 3: run DCA at the retry block
DCA::on_initialize(retry_block);

//Assert: schedule still alive and retry counter reset (= the trade succeeded)
assert!(DCA::schedules(schedule_id).is_some(), "schedule must survive retry");
assert_eq!(
DCA::retries_on_error(schedule_id),
0,
"retry should have succeeded and reset the counter"
);
});
}

fn assert_trade_failed_with_omnipool_insufficient_balance(schedule_id: u32) {
let expected: sp_runtime::DispatchError = pallet_omnipool::Error::<Runtime>::InsufficientBalance.into();
let events = last_hydra_events(20);
let found = events.iter().any(|e| {
matches!(
e,
RuntimeEvent::DCA(pallet_dca::Event::TradeFailed { id, error, .. })
if *id == schedule_id && *error == expected
)
});
assert!(
found,
"expected TradeFailed event with omnipool::InsufficientBalance for schedule {schedule_id}"
);
}
}

fn create_xyk_pool_with_amounts(asset_a: u32, amount_a: u128, asset_b: u32, amount_b: u128) {
Expand Down
2 changes: 1 addition & 1 deletion runtime/hydradx/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "hydradx-runtime"
version = "415.0.0"
version = "416.0.0"
authors = ["GalacticCouncil"]
edition = "2021"
license = "Apache 2.0"
Expand Down
5 changes: 5 additions & 0 deletions runtime/hydradx/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,11 @@ impl Contains<DispatchError> for RetryOnErrorForDca {
let errors: Vec<DispatchError> = vec![
pallet_omnipool::Error::<Runtime>::AssetNotFound.into(),
pallet_omnipool::Error::<Runtime>::NotAllowed.into(),
// Off-by-one rounding on aToken balanceOf can trip the omnipool's
// pre-trade ensure_can_withdraw. Retry on a later block — the aave
// liquidity index is timestamp-dependent, so the rounding boundary
// shifts and a later attempt may pass.
pallet_omnipool::Error::<Runtime>::InsufficientBalance.into(),
pallet_dispatcher::Error::<Runtime>::EvmOutOfGas.into(),
pallet_circuit_breaker::Error::<Runtime>::DepositLimitExceededForWhitelistedAccount.into(),
];
Expand Down
2 changes: 1 addition & 1 deletion runtime/hydradx/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: Cow::Borrowed("hydradx"),
impl_name: Cow::Borrowed("hydradx"),
authoring_version: 1,
spec_version: 415,
spec_version: 416,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
Expand Down
Loading