Skip to content

Commit f898a5a

Browse files
Merge branch 'gigahdx-lock' into gigahdx-liquidations
2 parents 3023220 + 9c84cb5 commit f898a5a

36 files changed

Lines changed: 2245 additions & 224 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: 6 additions & 4 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/gigahdx.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,38 @@ fn giga_unstake_should_create_pending_position_when_called() {
316316
});
317317
}
318318

319+
#[test]
320+
fn realize_yield_should_fold_accrued_into_principal_when_rate_increased() {
321+
TestNet::reset();
322+
hydra_live_ext(PATH_TO_SNAPSHOT).execute_with(|| {
323+
init_gigahdx();
324+
reset_giga_state_for_fixture();
325+
326+
let alice: AccountId = ALICE.into();
327+
assert_ok!(GigaHdx::giga_stake(RuntimeOrigin::signed(alice.clone()), 100 * UNITS));
328+
329+
// Inject yield into the gigapot so rate = (100 + 100) / 100 = 2.
330+
assert_ok!(Balances::force_set_balance(
331+
RawOrigin::Root.into(),
332+
GigaHdx::gigapot_account_id(),
333+
100 * UNITS,
334+
));
335+
336+
let rate_before = GigaHdx::exchange_rate();
337+
let stake_before = pallet_gigahdx::Stakes::<Runtime>::get(&alice).expect("stake exists");
338+
assert_eq!(stake_before.hdx, 100 * UNITS);
339+
340+
assert_ok!(GigaHdx::realize_yield(RuntimeOrigin::signed(alice.clone())));
341+
342+
let stake_after = pallet_gigahdx::Stakes::<Runtime>::get(&alice).expect("stake exists");
343+
assert_eq!(stake_after.hdx, 200 * UNITS);
344+
assert_eq!(stake_after.gigahdx, stake_before.gigahdx, "gigahdx unchanged");
345+
assert_eq!(locked_under_ghdx(&alice), 200 * UNITS);
346+
assert_eq!(Balances::free_balance(GigaHdx::gigapot_account_id()), 0);
347+
assert_eq!(GigaHdx::exchange_rate(), rate_before, "exchange rate unchanged");
348+
});
349+
}
350+
319351
#[test]
320352
fn unlock_should_release_lock_when_cooldown_elapsed() {
321353
TestNet::reset();

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> {

0 commit comments

Comments
 (0)