Skip to content

Commit e3aed13

Browse files
Merge pull request #1449 from galacticcouncil/fix/dca-rounding-error-from-omnipool
fix: dca rounding error from omnipool
2 parents 75d4717 + 84c7b28 commit e3aed13

7 files changed

Lines changed: 76 additions & 6 deletions

File tree

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.

integration-tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "runtime-integration-tests"
3-
version = "1.81.1"
3+
version = "1.82.0"
44
description = "Integration tests"
55
authors = ["GalacticCouncil"]
66
edition = "2021"
16.4 MB
Binary file not shown.

integration-tests/src/dca.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4860,7 +4860,16 @@ mod aave_atoken {
48604860
use super::*;
48614861
use hydradx_runtime::DCA;
48624862

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

48654874
//Ignored as snapshot too big
48664875
//To verify locally, download snapshot with command `./target/release/scraper save-storage --uri wss://paseo-rpc.play.hydration.cloud --at 0x3db005212a4ae320a2808c6813880b583dacbf7df60b0314420e88f4f2dfe989`
@@ -4885,6 +4894,62 @@ mod aave_atoken {
48854894
assert!(schedule.is_some());
48864895
});
48874896
}
4897+
4898+
use frame_support::traits::OnInitialize;
4899+
4900+
#[test]
4901+
fn dca_should_succeed_after_retry_when_insufficient_balance_error_due_to_off_by_one_error() {
4902+
TestNet::reset();
4903+
4904+
hydra_live_ext(PATH_TO_SNAPSHOT).execute_with(|| {
4905+
//Arrange
4906+
assert_eq!(hydradx_runtime::System::block_number(), 12309734);
4907+
let schedule_id = 30972;
4908+
assert!(DCA::schedules(schedule_id).is_some());
4909+
4910+
//Act 1: first attempt fails with InsufficientBalance and is retried
4911+
DCA::on_initialize(12309735);
4912+
assert_trade_failed_with_omnipool_insufficient_balance(schedule_id);
4913+
assert_eq!(
4914+
DCA::retries_on_error(schedule_id),
4915+
1,
4916+
"first attempt should have been retried"
4917+
);
4918+
let retry_block =
4919+
DCA::schedule_execution_block(schedule_id).expect("schedule should be replanned to a retry block");
4920+
4921+
//Act 2: simulate elapsed time so aave liquidity index drifts (rounding boundary moves)
4922+
let now = hydradx_runtime::Timestamp::get();
4923+
hydradx_runtime::Timestamp::set_timestamp(now + 240_000);
4924+
4925+
//Act 3: run DCA at the retry block
4926+
DCA::on_initialize(retry_block);
4927+
4928+
//Assert: schedule still alive and retry counter reset (= the trade succeeded)
4929+
assert!(DCA::schedules(schedule_id).is_some(), "schedule must survive retry");
4930+
assert_eq!(
4931+
DCA::retries_on_error(schedule_id),
4932+
0,
4933+
"retry should have succeeded and reset the counter"
4934+
);
4935+
});
4936+
}
4937+
4938+
fn assert_trade_failed_with_omnipool_insufficient_balance(schedule_id: u32) {
4939+
let expected: sp_runtime::DispatchError = pallet_omnipool::Error::<Runtime>::InsufficientBalance.into();
4940+
let events = last_hydra_events(20);
4941+
let found = events.iter().any(|e| {
4942+
matches!(
4943+
e,
4944+
RuntimeEvent::DCA(pallet_dca::Event::TradeFailed { id, error, .. })
4945+
if *id == schedule_id && *error == expected
4946+
)
4947+
});
4948+
assert!(
4949+
found,
4950+
"expected TradeFailed event with omnipool::InsufficientBalance for schedule {schedule_id}"
4951+
);
4952+
}
48884953
}
48894954

48904955
fn create_xyk_pool_with_amounts(asset_a: u32, amount_a: u128, asset_b: u32, amount_b: u128) {

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 = "415.0.0"
3+
version = "416.0.0"
44
authors = ["GalacticCouncil"]
55
edition = "2021"
66
license = "Apache 2.0"

runtime/hydradx/src/assets.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,11 @@ impl Contains<DispatchError> for RetryOnErrorForDca {
929929
let errors: Vec<DispatchError> = vec![
930930
pallet_omnipool::Error::<Runtime>::AssetNotFound.into(),
931931
pallet_omnipool::Error::<Runtime>::NotAllowed.into(),
932+
// Off-by-one rounding on aToken balanceOf can trip the omnipool's
933+
// pre-trade ensure_can_withdraw. Retry on a later block — the aave
934+
// liquidity index is timestamp-dependent, so the rounding boundary
935+
// shifts and a later attempt may pass.
936+
pallet_omnipool::Error::<Runtime>::InsufficientBalance.into(),
932937
pallet_dispatcher::Error::<Runtime>::EvmOutOfGas.into(),
933938
pallet_circuit_breaker::Error::<Runtime>::DepositLimitExceededForWhitelistedAccount.into(),
934939
];

runtime/hydradx/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
128128
spec_name: Cow::Borrowed("hydradx"),
129129
impl_name: Cow::Borrowed("hydradx"),
130130
authoring_version: 1,
131-
spec_version: 415,
131+
spec_version: 416,
132132
impl_version: 0,
133133
apis: RUNTIME_API_VERSIONS,
134134
transaction_version: 1,

0 commit comments

Comments
 (0)