From 35c4eb33731b61188798d1983052e7f4e9dfdb22 Mon Sep 17 00:00:00 2001 From: Ricardo Guilherme Schmidt <3esmit@gmail.com> Date: Mon, 29 Jun 2026 13:55:59 -0300 Subject: [PATCH] fix(stablecoin): bound redemption controller price --- artifacts/stablecoin-idl.json | 6 ++-- .../methods/guest/src/bin/stablecoin.rs | 2 ++ .../stablecoin/src/redemption_controller.rs | 30 +++++++++++++++++-- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/artifacts/stablecoin-idl.json b/artifacts/stablecoin-idl.json index d07fbee..b2e88be 100644 --- a/artifacts/stablecoin-idl.json +++ b/artifacts/stablecoin-idl.json @@ -118,9 +118,9 @@ "accounts": [ { "name": "controller", - "writable": false, + "writable": true, "signer": false, - "init": false + "init": true }, { "name": "stablecoin_definition", @@ -175,7 +175,7 @@ "accounts": [ { "name": "controller", - "writable": false, + "writable": true, "signer": false, "init": false }, diff --git a/programs/stablecoin/methods/guest/src/bin/stablecoin.rs b/programs/stablecoin/methods/guest/src/bin/stablecoin.rs index 1fcd50d..d651540 100644 --- a/programs/stablecoin/methods/guest/src/bin/stablecoin.rs +++ b/programs/stablecoin/methods/guest/src/bin/stablecoin.rs @@ -128,6 +128,7 @@ mod stablecoin { #[instruction] pub fn initialize_redemption_controller( ctx: ProgramContext, + #[account(init)] controller: AccountWithMetadata, stablecoin_definition: AccountWithMetadata, price_feed: AccountWithMetadata, @@ -172,6 +173,7 @@ mod stablecoin { #[instruction] pub fn update_redemption_controller( ctx: ProgramContext, + #[account(mut)] controller: AccountWithMetadata, price_feed: AccountWithMetadata, current_timestamp: u64, diff --git a/programs/stablecoin/src/redemption_controller.rs b/programs/stablecoin/src/redemption_controller.rs index f0783f6..a42081e 100644 --- a/programs/stablecoin/src/redemption_controller.rs +++ b/programs/stablecoin/src/redemption_controller.rs @@ -276,12 +276,13 @@ fn compute_next_controller_state( fn apply_redemption_rate(redemption_price: u128, redemption_rate: i128, elapsed: u64) -> u128 { let drift = redemption_rate.saturating_mul(i128::from(elapsed)); - if drift >= 0 { + let next_price = if drift >= 0 { redemption_price.saturating_add(u128::try_from(drift).unwrap_or(u128::MAX)) } else { let decrease = u128::try_from(drift.saturating_neg()).unwrap_or(u128::MAX); redemption_price.saturating_sub(decrease).max(1) - } + }; + next_price.min(i128_max_as_u128()) } fn price_error(redemption_price: u128, market_price: u128) -> i128 { @@ -582,6 +583,31 @@ mod tests { assert_eq!(updated.last_update_timestamp, 105); } + #[test] + fn controller_clamps_redemption_price_to_i128_max() { + let mut controller = controller_state(); + controller.redemption_price = i128_max_as_u128() - 5; + controller.redemption_rate = 10; + controller.proportional_gain = 0; + + let updated = compute_next_controller_state(&controller, i128_max_as_u128(), 101); + + assert_eq!(updated.redemption_price, i128_max_as_u128()); + assert_eq!(updated.redemption_rate, 0); + } + + #[test] + fn controller_clamps_existing_redemption_price_above_i128_max() { + let mut controller = controller_state(); + controller.redemption_price = i128_max_as_u128() + 5; + controller.redemption_rate = -1; + controller.proportional_gain = 0; + + let updated = compute_next_controller_state(&controller, 1, 101); + + assert_eq!(updated.redemption_price, i128_max_as_u128()); + } + #[test] fn controller_clamps_accumulated_error_and_redemption_rate() { let mut controller = controller_state();