Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions modules/stablecoin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,19 @@ includes the account ID in base58 and lowercase hexadecimal form plus
`controllerIntegralTerm`, and `lastUpdatedAt` as decimal strings. It does not
project the current redemption price or simulate a controller update.

### `currentGlobalState()`

Reads Protocol Parameters, Stability Fee Accumulator, Redemption Price State,
and the canonical `CLOCK_01` account through `lez_core`. It verifies each
stablecoin singleton's PDA, owner, and data before projecting both current
values at the clock account's Unix-millisecond timestamp.

The result contains `accumulatedRateAtLastAccrual`, `lastAccruedAt`,
`redemptionPriceAtLastUpdate`, `lastUpdatedAt`, `currentAccumulatedRate`,
`currentRedemptionPrice`, and `projectedAt`. Every value is an exact decimal
string. Projection uses saturating timestamp subtraction and the on-chain
seven-day compounding-window clamp. The method accepts no caller-provided time.

### `initializeProgram(request)`

Required request fields:
Expand Down
8 changes: 8 additions & 0 deletions modules/stablecoin/ffi/include/stablecoin_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ char *stablecoin_decode_stability_fee_accumulator(const char *request_json);
*/
char *stablecoin_decode_redemption_price_state(const char *request_json);

/**
* Validates stablecoin global accounts and projects their current values at `CLOCK_01`.
*
* # Safety
* `request_json` must be null or point to a live NUL-terminated byte string.
*/
char *stablecoin_current_global_state(const char *request_json);

/**
* Builds the exact wallet submission plan for `InitializeProgram`.
*
Expand Down
73 changes: 50 additions & 23 deletions modules/stablecoin/ffi/src/api/decode.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use lee_core::{account::AccountId, program::ProgramId};
use serde_json::{json, Value};
use stablecoin_core::{
compute_protocol_parameters_pda, compute_redemption_price_state_pda,
Expand All @@ -10,32 +11,61 @@ use super::{
DecodeRedemptionPriceStateRequest, DecodeStabilityFeeAccumulatorRequest, StablecoinApiError,
StablecoinResult,
};
use crate::account::{account_id_hex, decode_account};
use crate::{
account::{account_id_hex, decode_account},
AccountRead,
};

pub fn decode_protocol_parameters(request: DecodeProtocolParametersRequest) -> StablecoinResult {
let stablecoin_program_id = parse_stablecoin_program_id(&request.stablecoin_program_id)?;
let (account_id, account) = decode_account(&request.protocol_parameters)
.map_err(|_| StablecoinApiError::new("account_read_failed"))?;
let (account_id, parameters) =
validated_protocol_parameters(stablecoin_program_id, &request.protocol_parameters)?;
Ok(parameters_value(account_id, &parameters))
}

pub fn decode_stability_fee_accumulator(
request: DecodeStabilityFeeAccumulatorRequest,
) -> StablecoinResult {
let stablecoin_program_id = parse_stablecoin_program_id(&request.stablecoin_program_id)?;
let (account_id, accumulator) = validated_stability_fee_accumulator(
stablecoin_program_id,
&request.stability_fee_accumulator,
)?;
Ok(stability_fee_accumulator_value(account_id, &accumulator))
}

pub fn decode_redemption_price_state(
request: DecodeRedemptionPriceStateRequest,
) -> StablecoinResult {
let stablecoin_program_id = parse_stablecoin_program_id(&request.stablecoin_program_id)?;
let (account_id, state) =
validated_redemption_price_state(stablecoin_program_id, &request.redemption_price_state)?;
Ok(redemption_price_state_value(account_id, &state))
}

pub(super) fn validated_protocol_parameters(
stablecoin_program_id: ProgramId,
read: &AccountRead,
) -> Result<(AccountId, ProtocolParameters), StablecoinApiError> {
let (account_id, account) =
decode_account(read).map_err(|_| StablecoinApiError::new("account_read_failed"))?;
if account_id != compute_protocol_parameters_pda(stablecoin_program_id) {
return Err(StablecoinApiError::new("protocol_parameters_pda_mismatch"));
}
if account.program_owner != stablecoin_program_id {
return Err(StablecoinApiError::new("stablecoin_program_mismatch"));
}

let parameters = ProtocolParameters::try_from(&account.data)
.map_err(|_| StablecoinApiError::new("invalid_protocol_parameters_data"))?;
Ok(parameters_value(account_id, &parameters))
Ok((account_id, parameters))
}

pub fn decode_stability_fee_accumulator(
request: DecodeStabilityFeeAccumulatorRequest,
) -> StablecoinResult {
let stablecoin_program_id = parse_stablecoin_program_id(&request.stablecoin_program_id)?;
let (account_id, account) = decode_account(&request.stability_fee_accumulator)
.map_err(|_| StablecoinApiError::new("account_read_failed"))?;

pub(super) fn validated_stability_fee_accumulator(
stablecoin_program_id: ProgramId,
read: &AccountRead,
) -> Result<(AccountId, StabilityFeeAccumulator), StablecoinApiError> {
let (account_id, account) =
decode_account(read).map_err(|_| StablecoinApiError::new("account_read_failed"))?;
if account_id != compute_stability_fee_accumulator_pda(stablecoin_program_id) {
return Err(StablecoinApiError::new(
"stability_fee_accumulator_pda_mismatch",
Expand All @@ -44,19 +74,17 @@ pub fn decode_stability_fee_accumulator(
if account.program_owner != stablecoin_program_id {
return Err(StablecoinApiError::new("stablecoin_program_mismatch"));
}

let accumulator = StabilityFeeAccumulator::try_from(&account.data)
.map_err(|_| StablecoinApiError::new("invalid_stability_fee_accumulator_data"))?;
Ok(stability_fee_accumulator_value(account_id, &accumulator))
Ok((account_id, accumulator))
}

pub fn decode_redemption_price_state(
request: DecodeRedemptionPriceStateRequest,
) -> StablecoinResult {
let stablecoin_program_id = parse_stablecoin_program_id(&request.stablecoin_program_id)?;
let (account_id, account) = decode_account(&request.redemption_price_state)
.map_err(|_| StablecoinApiError::new("account_read_failed"))?;

pub(super) fn validated_redemption_price_state(
stablecoin_program_id: ProgramId,
read: &AccountRead,
) -> Result<(AccountId, RedemptionPriceState), StablecoinApiError> {
let (account_id, account) =
decode_account(read).map_err(|_| StablecoinApiError::new("account_read_failed"))?;
if account_id != compute_redemption_price_state_pda(stablecoin_program_id) {
return Err(StablecoinApiError::new(
"redemption_price_state_pda_mismatch",
Expand All @@ -65,10 +93,9 @@ pub fn decode_redemption_price_state(
if account.program_owner != stablecoin_program_id {
return Err(StablecoinApiError::new("stablecoin_program_mismatch"));
}

let state = RedemptionPriceState::try_from(&account.data)
.map_err(|_| StablecoinApiError::new("invalid_redemption_price_state_data"))?;
Ok(redemption_price_state_value(account_id, &state))
Ok((account_id, state))
}

fn parameters_value(
Expand Down
4 changes: 3 additions & 1 deletion modules/stablecoin/ffi/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
mod decode;
mod plan;
mod program;
mod projection;
mod request;

#[cfg(test)]
Expand All @@ -15,8 +16,9 @@ pub use decode::{
};
pub use plan::initialize_program_plan;
pub use program::program_info;
pub use projection::current_global_state;
pub use request::{
DecodeProtocolParametersRequest, DecodeRedemptionPriceStateRequest,
CurrentGlobalStateRequest, DecodeProtocolParametersRequest, DecodeRedemptionPriceStateRequest,
DecodeStabilityFeeAccumulatorRequest, InitializeProgramPlanRequest, ProgramInfoRequest,
};
use serde_json::Value;
Expand Down
Loading
Loading