fix(stablecoin): require controller initialization signer

This commit is contained in:
Ricardo Guilherme Schmidt 2026-06-29 15:03:39 -03:00
parent 5626f3af76
commit 7e4e9eeaf0
No known key found for this signature in database
GPG Key ID: 1396EA17DE132FFE
5 changed files with 41 additions and 6 deletions

View File

@ -125,7 +125,7 @@
{ {
"name": "stablecoin_definition", "name": "stablecoin_definition",
"writable": false, "writable": false,
"signer": false, "signer": true,
"init": false "init": false
}, },
{ {

View File

@ -27,6 +27,10 @@ impl Keys {
fn user_stablecoin_holding() -> PrivateKey { fn user_stablecoin_holding() -> PrivateKey {
PrivateKey::try_new([43; 32]).expect("valid private key") PrivateKey::try_new([43; 32]).expect("valid private key")
} }
fn stablecoin_definition() -> PrivateKey {
PrivateKey::try_new([44; 32]).expect("valid private key")
}
} }
impl Ids { impl Ids {
@ -51,7 +55,9 @@ impl Ids {
} }
fn stablecoin_definition() -> AccountId { fn stablecoin_definition() -> AccountId {
AccountId::new([6; 32]) AccountId::from(&PublicKey::new_from_private_key(
&Keys::stablecoin_definition(),
))
} }
fn price_feed() -> AccountId { fn price_feed() -> AccountId {
@ -486,11 +492,12 @@ fn stablecoin_redemption_controller_initializes_and_updates_from_price_feed() {
Ids::stablecoin_definition(), Ids::stablecoin_definition(),
Ids::price_feed(), Ids::price_feed(),
], ],
vec![], vec![current_nonce(&state, Ids::stablecoin_definition())],
initialize, initialize,
) )
.unwrap(); .unwrap();
let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); let witness_set =
public_transaction::WitnessSet::for_message(&message, &[&Keys::stablecoin_definition()]);
let tx = PublicTransaction::new(message, witness_set); let tx = PublicTransaction::new(message, witness_set);
state state
.transition_from_public_transaction(&tx, 0, current_timestamp) .transition_from_public_transaction(&tx, 0, current_timestamp)

View File

@ -91,7 +91,7 @@ pub enum Instruction {
/// Required accounts (3): /// Required accounts (3):
/// - Redemption controller account (uninitialized, address must match /// - Redemption controller account (uninitialized, address must match
/// `compute_redemption_controller_pda(self_program_id, stablecoin_definition, price_feed)`) /// `compute_redemption_controller_pda(self_program_id, stablecoin_definition, price_feed)`)
/// - Stablecoin token definition account (initialized fungible token) /// - Stablecoin token definition account (authorized initialized fungible token)
/// - Oracle price feed account (initialized; must decode as the configured /// - Oracle price feed account (initialized; must decode as the configured
/// stablecoin/collateral market price) /// stablecoin/collateral market price)
/// ///

View File

@ -130,6 +130,7 @@ mod stablecoin {
ctx: ProgramContext, ctx: ProgramContext,
#[account(init)] #[account(init)]
controller: AccountWithMetadata, controller: AccountWithMetadata,
#[account(signer)]
stablecoin_definition: AccountWithMetadata, stablecoin_definition: AccountWithMetadata,
price_feed: AccountWithMetadata, price_feed: AccountWithMetadata,
collateral_definition_id: AccountId, collateral_definition_id: AccountId,

View File

@ -16,6 +16,7 @@ const CONTROLLER_GAIN_SCALE_I128: i128 = {
/// Initialize the redemption-rate feedback controller for one stablecoin/feed pair. /// Initialize the redemption-rate feedback controller for one stablecoin/feed pair.
/// ///
/// # Panics /// # Panics
/// - `stablecoin_definition` is not authorized.
/// - `controller` is already initialized. /// - `controller` is already initialized.
/// - `controller.account_id` does not match the stablecoin/feed PDA. /// - `controller.account_id` does not match the stablecoin/feed PDA.
/// - `stablecoin_definition` is uninitialized or not a fungible token definition. /// - `stablecoin_definition` is uninitialized or not a fungible token definition.
@ -39,6 +40,10 @@ pub fn initialize_redemption_controller(
max_price_feed_age: u64, max_price_feed_age: u64,
current_timestamp: u64, current_timestamp: u64,
) -> Vec<AccountPostState> { ) -> Vec<AccountPostState> {
assert!(
stablecoin_definition.is_authorized,
"Stablecoin definition authorization is missing"
);
assert_eq!( assert_eq!(
controller.account, controller.account,
Account::default(), Account::default(),
@ -380,7 +385,7 @@ mod tests {
}), }),
nonce: Nonce(0), nonce: Nonce(0),
}, },
is_authorized: false, is_authorized: true,
account_id: stablecoin_definition_id(), account_id: stablecoin_definition_id(),
} }
} }
@ -489,6 +494,28 @@ mod tests {
assert_eq!(controller.last_update_timestamp, 100); assert_eq!(controller.last_update_timestamp, 100);
} }
#[test]
#[should_panic(expected = "Stablecoin definition authorization is missing")]
fn initialize_redemption_controller_requires_stablecoin_definition_authorization() {
let mut stablecoin_definition = stablecoin_definition_account();
stablecoin_definition.is_authorized = false;
initialize_redemption_controller(
uninit_controller_account(),
stablecoin_definition,
price_feed_account(1_000, 100),
STABLECOIN_PROGRAM_ID,
collateral_definition_id(),
1_000,
CONTROLLER_GAIN_SCALE,
0,
1_000,
500,
10,
100,
);
}
#[test] #[test]
fn update_redemption_controller_uses_live_price_feed() { fn update_redemption_controller_uses_live_price_feed() {
let post_states = update_redemption_controller( let post_states = update_redemption_controller(