mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 14:11:09 +00:00
219 lines
7.7 KiB
Rust
219 lines
7.7 KiB
Rust
//! Shared account builders for the poke host-function unit tests.
|
|
//!
|
|
//! The three pokes (`accrue_stability_fee`, `update_redemption_rate`,
|
|
//! `refresh_globals`) validate overlapping account sets, so their tests would
|
|
//! otherwise repeat the same fixtures three times.
|
|
|
|
use clock_core::{ClockAccountData, CLOCK_01_PROGRAM_ACCOUNT_ID};
|
|
use nssa_core::{
|
|
account::{Account, AccountId, AccountWithMetadata, Data, Nonce},
|
|
program::ProgramId,
|
|
};
|
|
use stablecoin_core::{
|
|
compute_protocol_parameters_pda, compute_redemption_price_state_pda,
|
|
compute_stability_fee_accumulator_pda, math::FIXED_POINT_ONE, ProtocolParameters,
|
|
RedemptionPriceState, StabilityFeeAccumulator,
|
|
};
|
|
use twap_oracle_core::OraclePriceAccount;
|
|
|
|
pub(crate) const STABLECOIN_PROGRAM_ID: ProgramId = [3u32; 8];
|
|
pub(crate) const ORACLE_PROGRAM_ID: ProgramId = [4u32; 8];
|
|
pub(crate) const CLOCK_PROGRAM_ID: ProgramId = [5u32; 8];
|
|
|
|
/// Timestamps are Unix milliseconds, matching the `CLOCK_01` account.
|
|
pub(crate) const T0: u64 = 1_700_000_000_000;
|
|
/// Ten minutes after `T0` — past the 300_000 ms rate-update interval below.
|
|
pub(crate) const NOW: u64 = T0 + 600_000;
|
|
|
|
/// ~5% annual in per-millisecond fixed-point, the same realistic rate the
|
|
/// `initialize_program` tests use.
|
|
pub(crate) const TEST_STABILITY_FEE_PER_MILLISECOND: u128 = FIXED_POINT_ONE + 1_500_000_000_000_000;
|
|
pub(crate) const TEST_MINIMUM_MILLISECONDS_BETWEEN_RATE_UPDATES: u64 = 300_000;
|
|
pub(crate) const TEST_MAXIMUM_ORACLE_PRICE_AGE_MILLISECONDS: u64 = 900_000;
|
|
|
|
/// Starting accumulator anchor: `1.0`.
|
|
pub(crate) const ACCUMULATOR_ANCHOR: u128 = FIXED_POINT_ONE;
|
|
/// Starting redemption price: `0.5` collateral per stablecoin.
|
|
pub(crate) const REDEMPTION_PRICE_ANCHOR: u128 = FIXED_POINT_ONE / 2;
|
|
/// A market price below the redemption anchor, so `error > 0` and a positive
|
|
/// `Kp` drives the rate above `FIXED_POINT_ONE`.
|
|
pub(crate) const MARKET_PRICE_BELOW_ANCHOR: u128 = FIXED_POINT_ONE / 4;
|
|
|
|
/// Knobs the poke tests actually vary. Everything else is fixed.
|
|
pub(crate) struct ParameterOverrides {
|
|
pub(crate) stability_fee_per_millisecond: u128,
|
|
pub(crate) controller_proportional_gain: i128,
|
|
pub(crate) controller_integral_gain: i128,
|
|
pub(crate) is_frozen: bool,
|
|
}
|
|
|
|
impl Default for ParameterOverrides {
|
|
fn default() -> Self {
|
|
Self {
|
|
stability_fee_per_millisecond: TEST_STABILITY_FEE_PER_MILLISECOND,
|
|
controller_proportional_gain: FIXED_POINT_ONE as i128,
|
|
controller_integral_gain: 0,
|
|
is_frozen: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn caller_id() -> AccountId {
|
|
AccountId::new([0xCA; 32])
|
|
}
|
|
pub(crate) fn admin_id() -> AccountId {
|
|
AccountId::new([0xA0; 32])
|
|
}
|
|
pub(crate) fn freeze_authority_id() -> AccountId {
|
|
AccountId::new([0xFE; 32])
|
|
}
|
|
pub(crate) fn stablecoin_definition_id() -> AccountId {
|
|
AccountId::new([0x10; 32])
|
|
}
|
|
pub(crate) fn collateral_definition_id() -> AccountId {
|
|
AccountId::new([0x20; 32])
|
|
}
|
|
pub(crate) fn oracle_id() -> AccountId {
|
|
AccountId::new([0x30; 32])
|
|
}
|
|
pub(crate) fn oracle_source_id() -> AccountId {
|
|
AccountId::new([0x31; 32])
|
|
}
|
|
|
|
pub(crate) fn protocol_parameters_id() -> AccountId {
|
|
compute_protocol_parameters_pda(STABLECOIN_PROGRAM_ID)
|
|
}
|
|
pub(crate) fn accumulator_id() -> AccountId {
|
|
compute_stability_fee_accumulator_pda(STABLECOIN_PROGRAM_ID)
|
|
}
|
|
pub(crate) fn redemption_price_state_id() -> AccountId {
|
|
compute_redemption_price_state_pda(STABLECOIN_PROGRAM_ID)
|
|
}
|
|
|
|
/// An authorized caller. Pokes are permissionless, so any signer works.
|
|
pub(crate) fn caller_account() -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account::default(),
|
|
is_authorized: true,
|
|
account_id: caller_id(),
|
|
}
|
|
}
|
|
|
|
/// An uninitialized account at `account_id` — the shape every "must be
|
|
/// initialized" assertion rejects.
|
|
pub(crate) fn uninitialized(account_id: AccountId) -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account::default(),
|
|
is_authorized: false,
|
|
account_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn protocol_parameters_account(overrides: ParameterOverrides) -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account {
|
|
program_owner: STABLECOIN_PROGRAM_ID,
|
|
balance: 0,
|
|
data: Data::from(&ProtocolParameters {
|
|
admin_account_id: admin_id(),
|
|
freeze_authority_account_id: freeze_authority_id(),
|
|
stablecoin_definition_id: stablecoin_definition_id(),
|
|
collateral_definition_id: collateral_definition_id(),
|
|
market_price_oracle_id: oracle_id(),
|
|
stability_fee_per_millisecond: overrides.stability_fee_per_millisecond,
|
|
controller_proportional_gain: overrides.controller_proportional_gain,
|
|
controller_integral_gain: overrides.controller_integral_gain,
|
|
minimum_collateralization_ratio: FIXED_POINT_ONE * 3 / 2,
|
|
minimum_milliseconds_between_rate_updates:
|
|
TEST_MINIMUM_MILLISECONDS_BETWEEN_RATE_UPDATES,
|
|
maximum_oracle_price_age_milliseconds: TEST_MAXIMUM_ORACLE_PRICE_AGE_MILLISECONDS,
|
|
is_frozen: overrides.is_frozen,
|
|
}),
|
|
nonce: Nonce(0),
|
|
},
|
|
is_authorized: false,
|
|
account_id: protocol_parameters_id(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn accumulator_account(anchor: u128, last_accrued_at: u64) -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account {
|
|
program_owner: STABLECOIN_PROGRAM_ID,
|
|
balance: 0,
|
|
data: Data::from(&StabilityFeeAccumulator {
|
|
accumulated_rate_at_last_accrual: anchor,
|
|
last_accrued_at,
|
|
}),
|
|
nonce: Nonce(0),
|
|
},
|
|
is_authorized: false,
|
|
account_id: accumulator_id(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn redemption_price_state_account(last_updated_at: u64) -> AccountWithMetadata {
|
|
redemption_price_state_account_with_integral_term(last_updated_at, 0)
|
|
}
|
|
|
|
pub(crate) fn redemption_price_state_account_with_integral_term(
|
|
last_updated_at: u64,
|
|
controller_integral_term: i128,
|
|
) -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account {
|
|
program_owner: STABLECOIN_PROGRAM_ID,
|
|
balance: 0,
|
|
data: Data::from(&RedemptionPriceState {
|
|
redemption_price_at_last_update: REDEMPTION_PRICE_ANCHOR,
|
|
redemption_rate_per_millisecond: FIXED_POINT_ONE,
|
|
controller_integral_term,
|
|
last_updated_at,
|
|
}),
|
|
nonce: Nonce(0),
|
|
},
|
|
is_authorized: false,
|
|
account_id: redemption_price_state_id(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn oracle_account(timestamp: u64, price: u128) -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account {
|
|
program_owner: ORACLE_PROGRAM_ID,
|
|
balance: 0,
|
|
data: Data::from(&OraclePriceAccount {
|
|
base_asset: stablecoin_definition_id(),
|
|
quote_asset: collateral_definition_id(),
|
|
price,
|
|
timestamp,
|
|
source_id: oracle_source_id(),
|
|
confidence_interval: 0,
|
|
}),
|
|
nonce: Nonce(0),
|
|
},
|
|
is_authorized: false,
|
|
account_id: oracle_id(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn clock_account(timestamp: u64) -> AccountWithMetadata {
|
|
AccountWithMetadata {
|
|
account: Account {
|
|
program_owner: CLOCK_PROGRAM_ID,
|
|
balance: 0,
|
|
data: Data::try_from(
|
|
ClockAccountData {
|
|
block_id: 0,
|
|
timestamp,
|
|
}
|
|
.to_bytes(),
|
|
)
|
|
.expect("clock data fits"),
|
|
nonce: Nonce(0),
|
|
},
|
|
is_authorized: false,
|
|
account_id: CLOCK_01_PROGRAM_ACCOUNT_ID,
|
|
}
|
|
}
|