Skip to content

Commit 9c84cb5

Browse files
Merge branch 'master' into gigahdx-lock
2 parents 0c00c31 + a4522b3 commit 9c84cb5

27 files changed

Lines changed: 1590 additions & 222 deletions

File tree

CLAUDE.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,68 @@ Types: `feat`, `fix`, `refactor`, `perf`, `test`, `docs`, `style`, `ci`, `build`
147147

148148
**Branches:** `fix/description` or `feat/description`
149149

150+
## Code comments and docs
151+
152+
Default to **no comment**. Only write one when the *why* is non-obvious — a hidden
153+
invariant, a surprising decision, a workaround. If removing the comment wouldn't
154+
confuse a future reader who can see the code, don't write it.
155+
156+
**Never restate what the code already says.** Well-named identifiers and types are
157+
the documentation. Comments that paraphrase the next line are noise.
158+
159+
### Module / file headers
160+
One paragraph max. State what lives here; don't enumerate every item or describe
161+
the flow step-by-step.
162+
163+
### Struct / enum field docs
164+
Skip the obvious (`pub unstaking: Balance`, `pub voters_count: u32`). Document a
165+
field only when its semantics are surprising — e.g. it stacks instead of replacing,
166+
must match an external balance, doubles as an idempotency signal.
167+
168+
### Error variants
169+
One line each, or none if the name already tells the story. Don't write a
170+
paragraph explaining the policy that produces the error — that belongs at the
171+
check site.
172+
173+
### Extrinsic docs
174+
Follow the Description / Parameters / Emits structure from the "Extrinsic
175+
documentation" section above, but keep the **Description to 1–2 lines plus at
176+
most one short paragraph** for genuinely load-bearing context. In particular:
177+
178+
- Do not enumerate `Error` variants in the Description — the `#[pallet::error]`
179+
enum is the source of truth.
180+
- Do not list internal implementation steps ("locks X, then mints Y, then calls
181+
Z"). The code shows that.
182+
- Keep the *why* of any non-obvious constraint (e.g. "refuses while stHDX is in
183+
circulation — outstanding aTokens would be stranded").
184+
185+
### Trait method docs
186+
One line. If the trait-level doc already explains the contract, leave method
187+
docs out entirely.
188+
189+
### Inline comments inside function bodies
190+
Reserve for:
191+
- Non-obvious invariants the next line relies on.
192+
- Why a defensive branch exists / why an error is intentionally swallowed.
193+
- Why an unusual construct (`drain_prefix(...).count()` to actually drain,
194+
`set_lock` vs `extend_lock`, pre-decrement before an external call) is correct.
195+
196+
Skip:
197+
- Narrating control flow ("// new record: increment voter count" above
198+
`voters_count += 1`).
199+
- Explaining what a well-named helper does at its call site.
200+
- Restating the assertion in the next `ensure!`.
201+
202+
### What to keep
203+
Comments that warn a future reader about something they would otherwise miss:
204+
- "Must match `LockableAToken.sol`'s `freeBalance` check"
205+
- "Saturating math — hooks must never block voting"
206+
- "Pool presence ⇔ allocation has run" (load-bearing idempotency signal)
207+
- "stHDX invariants — verify on AAVE config change: (1)…(2)…"
208+
209+
If in doubt, delete the comment and see if the code still reads. If it does,
210+
leave it out.
211+
150212
## Versioning
151213

152214
- **SemVer** on all crates — bump `Cargo.toml` version on changes

Cargo.lock

Lines changed: 4 additions & 3 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.84.0"
3+
version = "1.85.0"
44
description = "Integration tests"
55
authors = ["GalacticCouncil"]
66
edition = "2021"

integration-tests/src/omnipool_slip_fees.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,3 +449,87 @@ fn sequential_trades_accumulate_slip_within_block() {
449449
no_slip_drop
450450
);
451451
}
452+
453+
#[test]
454+
fn buy_succeeds_when_slip_cap_is_binding() {
455+
let buy_amount = 100 * UNITS;
456+
let tight_cap = Permill::from_parts(1000); // 0.1%
457+
458+
TestNet::reset();
459+
Hydra::execute_with(|| {
460+
init_omnipool();
461+
assert_ok!(Omnipool::set_slip_fee(
462+
RuntimeOrigin::root(),
463+
Some(SlipFeeConfig {
464+
max_slip_fee: tight_cap
465+
}),
466+
));
467+
468+
let trader = AccountId::from(BOB);
469+
assert_ok!(Currencies::update_balance(
470+
RuntimeOrigin::root(),
471+
trader.clone(),
472+
DAI,
473+
(10_000_000 * UNITS) as i128,
474+
));
475+
476+
let dai_before = Currencies::free_balance(DAI, &trader);
477+
let hdx_before = Currencies::free_balance(HDX, &trader);
478+
479+
assert_ok!(Omnipool::buy(
480+
RuntimeOrigin::signed(trader.clone()),
481+
HDX,
482+
DAI,
483+
buy_amount,
484+
u128::MAX,
485+
));
486+
487+
let hdx_received = Currencies::free_balance(HDX, &trader) - hdx_before;
488+
assert_eq!(hdx_received, buy_amount);
489+
490+
let dai_spent = dai_before - Currencies::free_balance(DAI, &trader);
491+
assert!(dai_spent > 0);
492+
});
493+
}
494+
495+
#[test]
496+
fn buy_with_lrna_succeeds_when_slip_cap_is_binding() {
497+
let buy_amount = 100 * UNITS;
498+
let tight_cap = Permill::from_parts(1000); // 0.1%
499+
500+
TestNet::reset();
501+
Hydra::execute_with(|| {
502+
init_omnipool();
503+
assert_ok!(Omnipool::set_slip_fee(
504+
RuntimeOrigin::root(),
505+
Some(SlipFeeConfig {
506+
max_slip_fee: tight_cap
507+
}),
508+
));
509+
510+
let trader = AccountId::from(BOB);
511+
assert_ok!(Currencies::update_balance(
512+
RuntimeOrigin::root(),
513+
trader.clone(),
514+
LRNA,
515+
(1_000_000 * UNITS) as i128,
516+
));
517+
518+
let lrna_before = Currencies::free_balance(LRNA, &trader);
519+
let dai_before = Currencies::free_balance(DAI, &trader);
520+
521+
assert_ok!(Omnipool::buy(
522+
RuntimeOrigin::signed(trader.clone()),
523+
DAI,
524+
LRNA,
525+
buy_amount,
526+
u128::MAX,
527+
));
528+
529+
let dai_received = Currencies::free_balance(DAI, &trader) - dai_before;
530+
assert_eq!(dai_received, buy_amount);
531+
532+
let lrna_spent = lrna_before - Currencies::free_balance(LRNA, &trader);
533+
assert!(lrna_spent > 0);
534+
});
535+
}

integration-tests/src/staking.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2508,7 +2508,7 @@ fn increase_stake_should_work_when_referendum_ongoing_and_votes_processed() {
25082508
}
25092509

25102510
#[test]
2511-
fn voting_on_next_referenda_should_process_votes() {
2511+
fn removing_vote_should_process_votes() {
25122512
TestNet::reset();
25132513
Hydra::execute_with(|| {
25142514
init_omnipool();
@@ -2569,27 +2569,41 @@ fn voting_on_next_referenda_should_process_votes() {
25692569

25702570
end_referendum();
25712571

2572+
let alice_position_id = pallet_staking::Pallet::<hydradx_runtime::Runtime>::get_user_position_id(&ALICE.into())
2573+
.unwrap()
2574+
.unwrap();
2575+
2576+
// Before remove_vote, the finished vote has not been settled: still recorded in Votes,
2577+
// no points awarded, and not present in VotesRewarded.
2578+
let position_before =
2579+
pallet_staking::Pallet::<hydradx_runtime::Runtime>::get_position(alice_position_id).unwrap();
2580+
assert!(
2581+
pallet_staking::Pallet::<hydradx_runtime::Runtime>::get_position_votes(alice_position_id)
2582+
.votes
2583+
.iter()
2584+
.any(|(idx, _)| *idx == r)
2585+
);
25722586
assert!(
25732587
pallet_staking::Pallet::<hydradx_runtime::Runtime>::processed_votes::<AccountId, u32>(ALICE.into(), r)
25742588
.is_none()
25752589
);
25762590

2577-
let r = begin_referendum();
2578-
assert_ok!(ConvictionVoting::vote(
2591+
assert_ok!(ConvictionVoting::remove_vote(
25792592
hydradx_runtime::RuntimeOrigin::signed(ALICE.into()),
2580-
r,
2581-
AccountVote::Standard {
2582-
vote: Vote {
2583-
aye: true,
2584-
conviction: Conviction::Locked6x,
2585-
},
2586-
balance: 1_000_000 * UNITS,
2587-
}
2593+
Some(ROOT_TRACK),
2594+
r
25882595
));
2596+
2597+
// After remove_vote, settlement has happened: vote removed from Votes and points awarded.
25892598
assert!(
2590-
pallet_staking::Pallet::<hydradx_runtime::Runtime>::processed_votes::<AccountId, u32>(ALICE.into(), 0)
2591-
.is_some()
2599+
!pallet_staking::Pallet::<hydradx_runtime::Runtime>::get_position_votes(alice_position_id)
2600+
.votes
2601+
.iter()
2602+
.any(|(idx, _)| *idx == r)
25922603
);
2604+
let position_after =
2605+
pallet_staking::Pallet::<hydradx_runtime::Runtime>::get_position(alice_position_id).unwrap();
2606+
assert!(position_after.get_action_points() > position_before.get_action_points());
25932607
});
25942608
}
25952609

math/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ license = 'Apache-2.0'
66
name = "hydra-dx-math"
77
description = "A collection of utilities to make performing liquidity pool calculations more convenient."
88
repository = 'https://github.com/galacticcouncil/hydradx-math'
9-
version = "13.2.1"
9+
version = "13.2.2"
1010

1111
[dependencies]
1212
primitive-types = { workspace = true }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
cc 22ef4648bbc715696260418b2fb3790628c7788f9258b8b9a4f704dc424ee536 # shrinks to asset_out = AssetReserveState { reserve: 190635865358630188, hub_reserve: 1430674154905521092, shares: 100000000000000000, protocol_shares: 100000000000000000 }, amount = 5307728205712486, asset_fee = Permill(300), imbalance = I129 { value: 8936367638875155, negative: true }
2+
cc 611689fb0af263ea5f079f78b2907a0d86a5a841e52c6ab6d4c781f5ba4b59d0 # shrinks to asset = AssetReserveState { reserve: 2951077894421946163, hub_reserve: 5203031156877507362, shares: 100000000000000000, protocol_shares: 100000000000000000 }, position = Position { amount: 1000000000, shares: 1000000000, price: (1763095357072293632, 1000000000000000000) }
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Seeds for failure cases proptest has generated in the past. It is
2+
# automatically read and these particular cases re-run before any
3+
# novel cases are generated.
4+
#
5+
# It is recommended to check this file in to source control so that
6+
# everyone who runs the test benefits from these saved cases.
7+
cc 25c9d46027af5640b9cce25c1970d5378de64752dd1c9520f5757251db333d27 # shrinks to pool = [AssetReserve { amount: 368323000000, decimals: 6 }, AssetReserve { amount: 380987625000000, decimals: 6 }, AssetReserve { amount: 488969232000000, decimals: 6 }, AssetReserve { amount: 12656000000, decimals: 6 }], amount = 241, amp = 6682, (idx_in, idx_out) = (1, 3)
8+
cc b8acd18b42aad7c2bf2fb1869de4f133a49e05a5fd165865f781cadd006488c9
9+
cc b8ffdc09a03ed41698bc59c46d68585ee05e238c14fd8d53d3b7fb0cedfabe3b
10+
cc 3b86f211644d954ca6a8212758cd01d579c3b1d0cfe5cc615ff25726df7d1fb0
11+
cc 63a30c69dc9a73f7cfc9c21fc4cc75e88026f721c3fa9b2b8eaa514e988e2649
12+
cc 464e37423f75de7afd23eef91ca5b097a1aa5e4f9bd839325571089ef256f924

math/src/omnipool/invariants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ fn high_asset_state() -> impl Strategy<Value = AssetReserveState<Balance>> {
6060
}
6161

6262
fn trade_amount() -> impl Strategy<Value = Balance> {
63-
1_000_000_000..10000 * ONE
63+
ONE / 10..10000 * ONE
6464
}
6565

6666
fn price() -> impl Strategy<Value = FixedU128> {

math/src/omnipool/math.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ pub fn calculate_buy_for_hub_asset_state_changes(
218218

219219
// Invert buy-side slip to find how much hub asset the user must provide
220220
let slip_buy_amount = if let Some(slip) = slip {
221-
let d_gross = invert_buy_side_slip(d_net, slip.asset_hub_reserve, slip.asset_delta)?;
221+
let d_gross = invert_buy_side_slip(d_net, slip.asset_hub_reserve, slip.asset_delta, slip.max_slip_fee)?;
222222
d_gross.checked_sub(d_net)?
223223
} else {
224224
0
@@ -277,14 +277,25 @@ pub fn calculate_buy_state_changes(
277277

278278
// Step 2: Invert buy-side slip to find D_gross from D_net
279279
let d_gross = if let Some(slip) = slip {
280-
invert_buy_side_slip(d_net, slip.asset_out_hub_reserve, slip.asset_out_delta)?
280+
invert_buy_side_slip(
281+
d_net,
282+
slip.asset_out_hub_reserve,
283+
slip.asset_out_delta,
284+
slip.max_slip_fee,
285+
)?
281286
} else {
282287
d_net
283288
};
284289

285290
// Step 3: Invert sell-side fees (protocol_fee + sell slip) to find delta_hub_reserve_in
286291
let delta_hub_reserve_in = if let Some(slip) = slip {
287-
invert_sell_side_fees(d_gross, protocol_fee, slip.asset_in_hub_reserve, slip.asset_in_delta)?
292+
invert_sell_side_fees(
293+
d_gross,
294+
protocol_fee,
295+
slip.asset_in_hub_reserve,
296+
slip.asset_in_delta,
297+
slip.max_slip_fee,
298+
)?
288299
} else {
289300
// No slip — original inversion
290301
FixedU128::from_inner(d_net)

0 commit comments

Comments
 (0)