mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-07-20 14:00:12 +00:00
fix(stablecoin): harden stability fee edge cases
This commit is contained in:
parent
e247365f30
commit
e4aa722d66
@ -123,8 +123,11 @@ pub fn generate_debt(
|
|||||||
oracle.price != 0,
|
oracle.price != 0,
|
||||||
"Market price oracle price must be non-zero"
|
"Market price oracle price must be non-zero"
|
||||||
);
|
);
|
||||||
|
let oracle_age = now
|
||||||
|
.checked_sub(oracle.timestamp)
|
||||||
|
.expect("Market price oracle timestamp is in the future");
|
||||||
assert!(
|
assert!(
|
||||||
now.saturating_sub(oracle.timestamp) <= params.maximum_oracle_price_age_milliseconds,
|
oracle_age <= params.maximum_oracle_price_age_milliseconds,
|
||||||
"Market price oracle is stale"
|
"Market price oracle is stale"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,8 @@ use crate::shared::{read_clock_timestamp, read_protocol_parameters};
|
|||||||
/// - `position` or `vault` is already initialized.
|
/// - `position` or `vault` is already initialized.
|
||||||
/// - `position.account_id` / `vault.account_id` do not match their PDA derivations.
|
/// - `position.account_id` / `vault.account_id` do not match their PDA derivations.
|
||||||
/// - `user_holding` cannot be decoded as a [`TokenHolding`].
|
/// - `user_holding` cannot be decoded as a [`TokenHolding`].
|
||||||
/// - `user_holding`'s definition does not match `token_definition`.
|
/// - `user_holding`'s definition does not match `collateral_definition`.
|
||||||
/// - `token_definition.program_owner` does not match `user_holding.program_owner`.
|
/// - `collateral_definition.program_owner` does not match `user_holding.program_owner`.
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::too_many_arguments,
|
clippy::too_many_arguments,
|
||||||
reason = "instruction surface passes explicit owner, position, vault, collateral, and protocol accounts"
|
reason = "instruction surface passes explicit owner, position, vault, collateral, and protocol accounts"
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use nssa_core::{
|
|||||||
use stablecoin_core::{
|
use stablecoin_core::{
|
||||||
compute_protocol_parameters_pda, compute_redemption_price_state_pda,
|
compute_protocol_parameters_pda, compute_redemption_price_state_pda,
|
||||||
compute_stability_fee_accumulator_pda, current_accumulated_rate, ProtocolParameters,
|
compute_stability_fee_accumulator_pda, current_accumulated_rate, ProtocolParameters,
|
||||||
RedemptionPriceState, StabilityFeeAccumulator, MAXIMUM_COMPOUNDING_WINDOW_MILLISECONDS,
|
RedemptionPriceState, StabilityFeeAccumulator,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) fn read_clock_timestamp(clock: &AccountWithMetadata) -> u64 {
|
pub(crate) fn read_clock_timestamp(clock: &AccountWithMetadata) -> u64 {
|
||||||
@ -84,17 +84,9 @@ pub(crate) fn accrue_stability_fee_state(
|
|||||||
params: &ProtocolParameters,
|
params: &ProtocolParameters,
|
||||||
now: u64,
|
now: u64,
|
||||||
) -> StabilityFeeAccumulator {
|
) -> StabilityFeeAccumulator {
|
||||||
let elapsed = now
|
|
||||||
.saturating_sub(accumulator.last_accrued_at)
|
|
||||||
.min(MAXIMUM_COMPOUNDING_WINDOW_MILLISECONDS);
|
|
||||||
let last_accrued_at = accumulator
|
|
||||||
.last_accrued_at
|
|
||||||
.checked_add(elapsed)
|
|
||||||
.expect("Clamped elapsed timestamp cannot overflow");
|
|
||||||
|
|
||||||
StabilityFeeAccumulator {
|
StabilityFeeAccumulator {
|
||||||
accumulated_rate_at_last_accrual: current_accumulated_rate(accumulator, params, now),
|
accumulated_rate_at_last_accrual: current_accumulated_rate(accumulator, params, now),
|
||||||
last_accrued_at,
|
last_accrued_at: now,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -685,7 +685,7 @@ fn accrue_stability_fee_clamps_elapsed_window() {
|
|||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
updated.last_accrued_at,
|
updated.last_accrued_at,
|
||||||
MAXIMUM_COMPOUNDING_WINDOW_MILLISECONDS
|
MAXIMUM_COMPOUNDING_WINDOW_MILLISECONDS + 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -992,6 +992,24 @@ fn generate_debt_rejects_uninitialized_market_price_oracle() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Market price oracle timestamp is in the future")]
|
||||||
|
fn generate_debt_rejects_future_market_price_oracle() {
|
||||||
|
crate::generate_debt::generate_debt(
|
||||||
|
owner_account(),
|
||||||
|
position_account(1_000, 0),
|
||||||
|
stablecoin_definition_account(0),
|
||||||
|
user_stablecoin_holding(0),
|
||||||
|
stability_fee_accumulator_account(FIXED_POINT_ONE, 1_000),
|
||||||
|
redemption_price_state_account(FIXED_POINT_ONE, 1_000),
|
||||||
|
oracle_account(1_001),
|
||||||
|
protocol_parameters_account(false),
|
||||||
|
clock_account(1_000),
|
||||||
|
STABLECOIN_PROGRAM_ID,
|
||||||
|
100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn repay_debt_uses_floor_rounding_against_current_accumulator() {
|
fn repay_debt_uses_floor_rounding_against_current_accumulator() {
|
||||||
let accumulator = FIXED_POINT_ONE + FIXED_POINT_ONE / 10;
|
let accumulator = FIXED_POINT_ONE + FIXED_POINT_ONE / 10;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user