Issue 01 — Frozen-check propagation in open_position, withdraw_collateral, generate_debt
Plan: Stablecoin Plan 5 — Emergency Freeze
Depends on: Plan 1 (the ProtocolParameters struct with is_frozen field, exposed from stablecoin_core); Plan 3 (the three target host functions exist in their full form, reading and decoding ProtocolParameters).
Blocks: issues 02 and 03 in this plan (the integration tests rely on the gate landed here).
Goal: Add a single assert!(!params.is_frozen, "Protocol is frozen") line to each of the three risk-increasing position instructions, immediately after the ProtocolParameters decode. Cover both directions of the new behaviour with focused unit tests (happy path when is_frozen = false, panic path when is_frozen = true) and one zkVM integration test that proves the gate works end-to-end.
Spec reference
Why
Once freeze (issue 02) flips ProtocolParameters.is_frozen = true, the protocol's three risk-increasing operations must stop accepting new state transitions until unfreeze flips it back. Without this gate, calling freeze would be a no-op — users could keep opening positions, withdrawing collateral, and minting debt while the operator believes the protocol is halted. Landing the gate BEFORE freeze ships (issue 02) keeps the kill switch real from the moment it ships.
The check is deliberately placed AFTER the existing ProtocolParameters decode, not before it: a malformed or wrong protocol_parameters input should still surface its own panic message (helpful to debug bad call sites) rather than be masked by a frozen panic the caller wouldn't be hitting in practice.
Architecture
For each of the three target host functions, find the line where ProtocolParameters is decoded from the protocol_parameters account — the line will look like:
let params = ProtocolParameters::try_from(&protocol_parameters.account.data)
.expect("ProtocolParameters must decode");
and insert immediately after it:
assert!(!params.is_frozen, "Protocol is frozen");
Three identical insertions, one panic message. No new helpers — the check is one line and inlining it keeps the call site readable. (If a future instruction grows another frozen-check, we can extract a assert_not_frozen(¶ms) helper; not worth the indirection at three sites.)
The integration test lives in programs/integration_tests/tests/stablecoin.rs and reuses the existing scaffolding (Keys, Ids, etc.) that Plan 3 introduces. It bootstraps the protocol via initialize_program, manually flips is_frozen on the ProtocolParameters account state (since freeze ships in issue 02), then tries open_position and asserts the zkVM rejects the transaction.
Files
- Modify:
programs/stablecoin/src/open_position.rs — one new assert! line, plus a // see spec §10.4 — frozen check comment.
- Modify:
programs/stablecoin/src/withdraw_collateral.rs — one new assert! line, plus the same shape of comment for §10.6.
- Modify:
programs/stablecoin/src/generate_debt.rs — one new assert! line, plus the same shape of comment for §10.7.
- Modify:
programs/stablecoin/src/tests/open_position_tests.rs (or whatever file Plan 3 created — confirm filename in Step 1) — append a happy-path-when-not-frozen sanity test (if not already exhaustive) and a #[should_panic(expected = "Protocol is frozen")] test.
- Modify:
programs/stablecoin/src/tests/withdraw_collateral_tests.rs — same shape of tests for §10.6.
- Modify:
programs/stablecoin/src/tests/generate_debt_tests.rs — same shape of tests for §10.7.
- Modify:
programs/integration_tests/tests/stablecoin.rs — append one new test, stablecoin_freeze_gate_blocks_open_position, that proves the panic message surfaces through the zkVM. (Issue 03 adds the broader stablecoin_freeze_blocks_risky_ops and stablecoin_unfreeze_resumes_normal_operation tests once freeze itself exists.)
Acceptance criteria
cargo test -p stablecoin_program open_position_tests::rejects_when_frozen passes.
cargo test -p stablecoin_program withdraw_collateral_tests::rejects_when_frozen passes.
cargo test -p stablecoin_program generate_debt_tests::rejects_when_frozen passes.
cargo test -p stablecoin_program is green overall.
RISC0_DEV_MODE=1 cargo test -p integration_tests --test stablecoin stablecoin_freeze_gate_blocks_open_position passes.
make clippy is green.
deposit_collateral, repay_debt, and close_position host functions are UNCHANGED — verify by git diff programs/stablecoin/src/deposit_collateral.rs programs/stablecoin/src/repay_debt.rs programs/stablecoin/src/close_position.rs showing no output.
Implementation steps
The insertion point depends on Plan 3's final shape. Run:
grep -n "ProtocolParameters::try_from" \
programs/stablecoin/src/open_position.rs \
programs/stablecoin/src/withdraw_collateral.rs \
programs/stablecoin/src/generate_debt.rs
Expected: one line per file, near the top of the function body, binding the decoded struct to a local. Note the binding name — it will most likely be params or protocol_params. Plan 3's stylistic choice wins. Below this issue uses params; replace with whatever Plan 3 actually used. Confirm the names match before editing.
Also list the existing test files so the next steps reference the right filenames:
ls programs/stablecoin/src/tests/
Expected: mod.rs, existing.rs, initialize_program_tests.rs, open_position_tests.rs, withdraw_collateral_tests.rs, generate_debt_tests.rs, plus other Plan 3 / Plan 2 files. Confirm the three test filenames; the steps below use these names verbatim.
In programs/stablecoin/src/open_position.rs, find:
let params = ProtocolParameters::try_from(&protocol_parameters.account.data)
.expect("ProtocolParameters must decode");
Insert immediately after it:
// Spec §10.4 — open_position is blocked when the protocol is frozen.
assert!(!params.is_frozen, "Protocol is frozen");
Also update the # Panics doc comment at the top of the function to add a bullet:
/// - `protocol_parameters.is_frozen = true` (protocol is frozen, see spec §10.4).
Run cargo check -p stablecoin_program. Expected: clean.
In programs/stablecoin/src/withdraw_collateral.rs, find the same ProtocolParameters::try_from(...) line and insert immediately after:
// Spec §10.6 — withdraw_collateral is blocked when the protocol is frozen.
assert!(!params.is_frozen, "Protocol is frozen");
Update the # Panics doc:
/// - `protocol_parameters.is_frozen = true` (protocol is frozen, see spec §10.6).
Run cargo check -p stablecoin_program. Expected: clean.
In programs/stablecoin/src/generate_debt.rs, find the same line and insert:
// Spec §10.7 — generate_debt is blocked when the protocol is frozen.
assert!(!params.is_frozen, "Protocol is frozen");
Update the # Panics doc:
/// - `protocol_parameters.is_frozen = true` (protocol is frozen, see spec §10.7).
Run cargo check -p stablecoin_program. Expected: clean.
git diff --name-only programs/stablecoin/src/
Expected output set: only open_position.rs, withdraw_collateral.rs, generate_debt.rs. If any of deposit_collateral.rs, repay_debt.rs, close_position.rs show up, revert those edits — they are allowed when frozen by design (spec §10.5, §10.8, §10.9 and §16.2). The check belongs only in the three risk-increasing instructions.
In programs/stablecoin/src/tests/open_position_tests.rs, find (or add, if Plan 3 left the file thin) a small helper that produces a default ProtocolParameters account input. The helper most likely already exists with a name like protocol_parameters_account() returning an AccountWithMetadata whose account.data decodes to ProtocolParameters { is_frozen: false, .. }. If it doesn't expose is_frozen directly, give it a builder-style override:
fn protocol_parameters_account_with_is_frozen(is_frozen: bool) -> nssa_core::account::AccountWithMetadata {
use nssa_core::account::Data;
use stablecoin_core::ProtocolParameters;
let mut decoded = decoded_default_protocol_parameters(); // helper Plan 3 ships
decoded.is_frozen = is_frozen;
let mut acc = protocol_parameters_account();
acc.account.data = Data::from(&decoded);
acc
}
Then append:
#[test]
fn happy_path_succeeds_when_protocol_is_not_frozen() {
// Sanity check: the default fixture has is_frozen = false and the call succeeds.
let (post_states, chained_calls) = invoke_open_position_with_protocol_parameters(
protocol_parameters_account_with_is_frozen(false),
);
assert_eq!(post_states.len(), EXPECTED_POST_STATE_COUNT);
assert_eq!(chained_calls.len(), EXPECTED_CHAINED_CALL_COUNT);
}
EXPECTED_POST_STATE_COUNT and EXPECTED_CHAINED_CALL_COUNT are whatever Plan 3 documents (6 post-states + 2 chained calls based on §10.4). invoke_open_position_with_protocol_parameters is a small helper that takes the protocol-parameters account and threads it into open_position(...) with all the other inputs pulled from existing default fixtures. If Plan 3 named its invocation helper differently, use that name verbatim instead of writing a new one.
Run cargo test -p stablecoin_program open_position_tests::happy_path_succeeds_when_protocol_is_not_frozen. Expected: PASS.
Append:
#[test]
#[should_panic(expected = "Protocol is frozen")]
fn rejects_open_position_when_protocol_is_frozen() {
let _ = invoke_open_position_with_protocol_parameters(
protocol_parameters_account_with_is_frozen(true),
);
}
Run cargo test -p stablecoin_program open_position_tests::rejects_open_position_when_protocol_is_frozen. Expected: PASS.
In programs/stablecoin/src/tests/withdraw_collateral_tests.rs, mirror steps 6 and 7. The helper / invocation function names follow Plan 3's; the two new tests should be:
#[test]
fn happy_path_succeeds_when_protocol_is_not_frozen() {
let (post_states, chained_calls) = invoke_withdraw_collateral_with_protocol_parameters(
protocol_parameters_account_with_is_frozen(false),
);
assert_eq!(post_states.len(), EXPECTED_POST_STATE_COUNT);
assert_eq!(chained_calls.len(), EXPECTED_CHAINED_CALL_COUNT);
}
#[test]
#[should_panic(expected = "Protocol is frozen")]
fn rejects_withdraw_collateral_when_protocol_is_frozen() {
let _ = invoke_withdraw_collateral_with_protocol_parameters(
protocol_parameters_account_with_is_frozen(true),
);
}
Run cargo test -p stablecoin_program withdraw_collateral_tests::. Expected: green including the two new tests.
In programs/stablecoin/src/tests/generate_debt_tests.rs, mirror steps 6 and 7:
#[test]
fn happy_path_succeeds_when_protocol_is_not_frozen() {
let (post_states, chained_calls) = invoke_generate_debt_with_protocol_parameters(
protocol_parameters_account_with_is_frozen(false),
);
assert_eq!(post_states.len(), EXPECTED_POST_STATE_COUNT);
assert_eq!(chained_calls.len(), EXPECTED_CHAINED_CALL_COUNT);
}
#[test]
#[should_panic(expected = "Protocol is frozen")]
fn rejects_generate_debt_when_protocol_is_frozen() {
let _ = invoke_generate_debt_with_protocol_parameters(
protocol_parameters_account_with_is_frozen(true),
);
}
Run cargo test -p stablecoin_program generate_debt_tests::. Expected: green including the two new tests.
In programs/integration_tests/tests/stablecoin.rs, append:
#[test]
fn stablecoin_freeze_gate_blocks_open_position() {
// Build the standard initialized protocol + funded user (helpers from Plan 3
// already in this file), then mutate the ProtocolParameters state to set
// is_frozen = true BEFORE the open_position transaction. `freeze` itself
// ships in plan 5 issue 02; this test exists to verify the gate landed
// here is honored by the zkVM, independent of how the flip happens.
let mut state = bootstrapped_state();
set_is_frozen_in_state(&mut state, true);
let owner_key = Keys::owner();
let tx: PublicTransaction = open_position_transaction(
&state,
&owner_key,
Balances::collateral_deposit(),
);
let outcome = state.try_apply_public_transaction(&tx);
let err = outcome.expect_err("freeze gate must reject open_position");
let formatted = format!("{err:?}");
assert!(
formatted.contains("Protocol is frozen"),
"expected 'Protocol is frozen' in error, got: {formatted}"
);
}
Three of the names above must match what Plan 3 introduced in this file. Specifically:
bootstrapped_state() — a helper that returns a V03State with the stablecoin program deployed and initialize_program already applied. Plan 3 introduced one when it landed the lifecycle integration tests; reuse it. If the name differs, swap.
open_position_transaction(state, owner_key, amount) — a helper that builds a signed PublicTransaction for the OpenPosition instruction. Reuse Plan 3's name verbatim.
set_is_frozen_in_state(state, true) — a helper this test introduces. Add it to the same file:
fn set_is_frozen_in_state(state: &mut V03State, is_frozen: bool) {
use stablecoin_core::{compute_protocol_parameters_pda, ProtocolParameters};
let pp_id = compute_protocol_parameters_pda(Ids::stablecoin_program());
let mut acc = state
.read_account(&pp_id)
.expect("ProtocolParameters must exist after bootstrap")
.clone();
let mut decoded = ProtocolParameters::try_from(&acc.data)
.expect("ProtocolParameters must decode");
decoded.is_frozen = is_frozen;
acc.data = nssa_core::account::Data::from(&decoded);
state
.force_write_account(pp_id, acc)
.expect("test harness must allow forcing the ProtocolParameters state");
}
The force_write_account API on V03State is the test-only override LEZ exposes for fixtures. If the harness Plan 3 uses calls it something different (overwrite_account, set_account_for_test, etc.), use that name verbatim. Confirm by grepping the existing test file:
grep -n "force_write_account\|overwrite_account\|set_account_for_test" \
programs/integration_tests/tests/stablecoin.rs
Run:
RISC0_DEV_MODE=1 cargo test -p integration_tests --test stablecoin \
stablecoin_freeze_gate_blocks_open_position
Expected: PASS.
make clippy
RISC0_DEV_MODE=1 cargo test -p stablecoin_program
RISC0_DEV_MODE=1 cargo test -p integration_tests --test stablecoin
Expected: all green.
git add programs/stablecoin/src/open_position.rs \
programs/stablecoin/src/withdraw_collateral.rs \
programs/stablecoin/src/generate_debt.rs \
programs/stablecoin/src/tests/open_position_tests.rs \
programs/stablecoin/src/tests/withdraw_collateral_tests.rs \
programs/stablecoin/src/tests/generate_debt_tests.rs \
programs/integration_tests/tests/stablecoin.rs
cargo +nightly fmt --all
git add -u
git commit -m "feat(stablecoin): propagate is_frozen gate into open_position, withdraw_collateral, generate_debt
Add an assert!(!params.is_frozen, \"Protocol is frozen\") line to the
three risk-increasing position instructions, immediately after the
ProtocolParameters decode. deposit_collateral, repay_debt, and
close_position remain allowed when frozen by design — see spec
§10.5 / §10.8 / §10.9 and the emergency walkthrough in §16.2.
Unit tests: six new tests (happy path + frozen-panic for each of the
three host functions). Integration test: one new zkVM-level test that
flips is_frozen on the ProtocolParameters state and verifies
open_position is rejected with the expected message.
The freeze and unfreeze instructions themselves ship in the next
issue in plan 5; this gate is what makes them effective once they
ship."
Notes for reviewer / future maintainers
- The frozen check is intentionally placed AFTER the
ProtocolParameters decode. If the decode itself fails (bad account input), the caller deserves the precise decode error, not a frozen-protocol error that wouldn't be the actual cause.
- The integration test uses a state-fixture override (
force_write_account / equivalent) rather than calling freeze because freeze ships in issue 02. This test deliberately scopes itself to one thing: that the gate landed here is real once is_frozen = true is observed in the on-chain state. Issue 03 will add tests that flip the flag via the real freeze instruction end-to-end.
- One panic message string (
"Protocol is frozen") shared across all three sites. If you ever want per-instruction differentiation (e.g. for telemetry), change all three at once and update the three #[should_panic(expected = ...)] attributes plus the integration test's contains(...) assertion.
- No new dependencies. No new crate features. No new public API on
stablecoin_core beyond what Plan 1 already exposed (ProtocolParameters with is_frozen: bool).
Dependencies
Depends on: Plan 1 (#156, is_frozen field); Plan 3 (#173, the three target host functions).
Blocks: #189, #190.
Issue 01 — Frozen-check propagation in
open_position,withdraw_collateral,generate_debtPlan: Stablecoin Plan 5 — Emergency Freeze
Depends on: Plan 1 (the
ProtocolParametersstruct withis_frozenfield, exposed fromstablecoin_core); Plan 3 (the three target host functions exist in their full form, reading and decodingProtocolParameters).Blocks: issues 02 and 03 in this plan (the integration tests rely on the gate landed here).
Goal: Add a single
assert!(!params.is_frozen, "Protocol is frozen")line to each of the three risk-increasing position instructions, immediately after theProtocolParametersdecode. Cover both directions of the new behaviour with focused unit tests (happy path whenis_frozen = false, panic path whenis_frozen = true) and one zkVM integration test that proves the gate works end-to-end.Spec reference
§10.4 open_position— "Panics if ...protocol_parameters.is_frozen = true."§10.6 withdraw_collateral— same panic condition.§10.7 generate_debt— same panic condition.§10.5 deposit_collateral— "Allowed when frozen." (DO NOT add a check here.)§10.8 repay_debt— "Allowed when frozen." (DO NOT add a check here.)§10.9 close_position— implicitly allowed when frozen (no frozen panic listed). (DO NOT add a check here.)§16.2 Emergency freeze— the end-to-end scenario the integration test mirrors in shape.Why
Once
freeze(issue 02) flipsProtocolParameters.is_frozen = true, the protocol's three risk-increasing operations must stop accepting new state transitions untilunfreezeflips it back. Without this gate, callingfreezewould be a no-op — users could keep opening positions, withdrawing collateral, and minting debt while the operator believes the protocol is halted. Landing the gate BEFOREfreezeships (issue 02) keeps the kill switch real from the moment it ships.The check is deliberately placed AFTER the existing
ProtocolParametersdecode, not before it: a malformed or wrongprotocol_parametersinput should still surface its own panic message (helpful to debug bad call sites) rather than be masked by a frozen panic the caller wouldn't be hitting in practice.Architecture
For each of the three target host functions, find the line where
ProtocolParametersis decoded from theprotocol_parametersaccount — the line will look like:and insert immediately after it:
Three identical insertions, one panic message. No new helpers — the check is one line and inlining it keeps the call site readable. (If a future instruction grows another frozen-check, we can extract a
assert_not_frozen(¶ms)helper; not worth the indirection at three sites.)The integration test lives in
programs/integration_tests/tests/stablecoin.rsand reuses the existing scaffolding (Keys,Ids, etc.) that Plan 3 introduces. It bootstraps the protocol viainitialize_program, manually flipsis_frozenon theProtocolParametersaccount state (sincefreezeships in issue 02), then triesopen_positionand asserts the zkVM rejects the transaction.Files
programs/stablecoin/src/open_position.rs— one newassert!line, plus a// see spec §10.4 — frozen checkcomment.programs/stablecoin/src/withdraw_collateral.rs— one newassert!line, plus the same shape of comment for §10.6.programs/stablecoin/src/generate_debt.rs— one newassert!line, plus the same shape of comment for §10.7.programs/stablecoin/src/tests/open_position_tests.rs(or whatever file Plan 3 created — confirm filename in Step 1) — append a happy-path-when-not-frozen sanity test (if not already exhaustive) and a#[should_panic(expected = "Protocol is frozen")]test.programs/stablecoin/src/tests/withdraw_collateral_tests.rs— same shape of tests for §10.6.programs/stablecoin/src/tests/generate_debt_tests.rs— same shape of tests for §10.7.programs/integration_tests/tests/stablecoin.rs— append one new test,stablecoin_freeze_gate_blocks_open_position, that proves the panic message surfaces through the zkVM. (Issue 03 adds the broaderstablecoin_freeze_blocks_risky_opsandstablecoin_unfreeze_resumes_normal_operationtests oncefreezeitself exists.)Acceptance criteria
cargo test -p stablecoin_program open_position_tests::rejects_when_frozenpasses.cargo test -p stablecoin_program withdraw_collateral_tests::rejects_when_frozenpasses.cargo test -p stablecoin_program generate_debt_tests::rejects_when_frozenpasses.cargo test -p stablecoin_programis green overall.RISC0_DEV_MODE=1 cargo test -p integration_tests --test stablecoin stablecoin_freeze_gate_blocks_open_positionpasses.make clippyis green.deposit_collateral,repay_debt, andclose_positionhost functions are UNCHANGED — verify bygit diff programs/stablecoin/src/deposit_collateral.rs programs/stablecoin/src/repay_debt.rs programs/stablecoin/src/close_position.rsshowing no output.Implementation steps
ProtocolParametersThe insertion point depends on Plan 3's final shape. Run:
grep -n "ProtocolParameters::try_from" \ programs/stablecoin/src/open_position.rs \ programs/stablecoin/src/withdraw_collateral.rs \ programs/stablecoin/src/generate_debt.rsExpected: one line per file, near the top of the function body, binding the decoded struct to a local. Note the binding name — it will most likely be
paramsorprotocol_params. Plan 3's stylistic choice wins. Below this issue usesparams; replace with whatever Plan 3 actually used. Confirm the names match before editing.Also list the existing test files so the next steps reference the right filenames:
Expected:
mod.rs,existing.rs,initialize_program_tests.rs,open_position_tests.rs,withdraw_collateral_tests.rs,generate_debt_tests.rs, plus other Plan 3 / Plan 2 files. Confirm the three test filenames; the steps below use these names verbatim.open_positionIn
programs/stablecoin/src/open_position.rs, find:Insert immediately after it:
Also update the
# Panicsdoc comment at the top of the function to add a bullet:/// - `protocol_parameters.is_frozen = true` (protocol is frozen, see spec §10.4).Run
cargo check -p stablecoin_program. Expected: clean.withdraw_collateralIn
programs/stablecoin/src/withdraw_collateral.rs, find the sameProtocolParameters::try_from(...)line and insert immediately after:Update the
# Panicsdoc:/// - `protocol_parameters.is_frozen = true` (protocol is frozen, see spec §10.6).Run
cargo check -p stablecoin_program. Expected: clean.generate_debtIn
programs/stablecoin/src/generate_debt.rs, find the same line and insert:Update the
# Panicsdoc:/// - `protocol_parameters.is_frozen = true` (protocol is frozen, see spec §10.7).Run
cargo check -p stablecoin_program. Expected: clean.deposit_collateral,repay_debt,close_positionwere NOT touchedExpected output set: only
open_position.rs,withdraw_collateral.rs,generate_debt.rs. If any ofdeposit_collateral.rs,repay_debt.rs,close_position.rsshow up, revert those edits — they are allowed when frozen by design (spec §10.5, §10.8, §10.9 and §16.2). The check belongs only in the three risk-increasing instructions.open_positionhappy path when not frozenIn
programs/stablecoin/src/tests/open_position_tests.rs, find (or add, if Plan 3 left the file thin) a small helper that produces a defaultProtocolParametersaccount input. The helper most likely already exists with a name likeprotocol_parameters_account()returning anAccountWithMetadatawhoseaccount.datadecodes toProtocolParameters { is_frozen: false, .. }. If it doesn't exposeis_frozendirectly, give it a builder-style override:Then append:
EXPECTED_POST_STATE_COUNTandEXPECTED_CHAINED_CALL_COUNTare whatever Plan 3 documents (6 post-states + 2 chained calls based on §10.4).invoke_open_position_with_protocol_parametersis a small helper that takes the protocol-parameters account and threads it intoopen_position(...)with all the other inputs pulled from existing default fixtures. If Plan 3 named its invocation helper differently, use that name verbatim instead of writing a new one.Run
cargo test -p stablecoin_program open_position_tests::happy_path_succeeds_when_protocol_is_not_frozen. Expected: PASS.open_positionpanics when frozenAppend:
Run
cargo test -p stablecoin_program open_position_tests::rejects_open_position_when_protocol_is_frozen. Expected: PASS.withdraw_collateralIn
programs/stablecoin/src/tests/withdraw_collateral_tests.rs, mirror steps 6 and 7. The helper / invocation function names follow Plan 3's; the two new tests should be:Run
cargo test -p stablecoin_program withdraw_collateral_tests::. Expected: green including the two new tests.generate_debtIn
programs/stablecoin/src/tests/generate_debt_tests.rs, mirror steps 6 and 7:Run
cargo test -p stablecoin_program generate_debt_tests::. Expected: green including the two new tests.In
programs/integration_tests/tests/stablecoin.rs, append:Three of the names above must match what Plan 3 introduced in this file. Specifically:
bootstrapped_state()— a helper that returns aV03Statewith the stablecoin program deployed andinitialize_programalready applied. Plan 3 introduced one when it landed the lifecycle integration tests; reuse it. If the name differs, swap.open_position_transaction(state, owner_key, amount)— a helper that builds a signedPublicTransactionfor theOpenPositioninstruction. Reuse Plan 3's name verbatim.set_is_frozen_in_state(state, true)— a helper this test introduces. Add it to the same file:The
force_write_accountAPI onV03Stateis the test-only override LEZ exposes for fixtures. If the harness Plan 3 uses calls it something different (overwrite_account,set_account_for_test, etc.), use that name verbatim. Confirm by grepping the existing test file:grep -n "force_write_account\|overwrite_account\|set_account_for_test" \ programs/integration_tests/tests/stablecoin.rsRun:
RISC0_DEV_MODE=1 cargo test -p integration_tests --test stablecoin \ stablecoin_freeze_gate_blocks_open_positionExpected: PASS.
Expected: all green.
git add programs/stablecoin/src/open_position.rs \ programs/stablecoin/src/withdraw_collateral.rs \ programs/stablecoin/src/generate_debt.rs \ programs/stablecoin/src/tests/open_position_tests.rs \ programs/stablecoin/src/tests/withdraw_collateral_tests.rs \ programs/stablecoin/src/tests/generate_debt_tests.rs \ programs/integration_tests/tests/stablecoin.rs cargo +nightly fmt --all git add -u git commit -m "feat(stablecoin): propagate is_frozen gate into open_position, withdraw_collateral, generate_debt Add an assert!(!params.is_frozen, \"Protocol is frozen\") line to the three risk-increasing position instructions, immediately after the ProtocolParameters decode. deposit_collateral, repay_debt, and close_position remain allowed when frozen by design — see spec §10.5 / §10.8 / §10.9 and the emergency walkthrough in §16.2. Unit tests: six new tests (happy path + frozen-panic for each of the three host functions). Integration test: one new zkVM-level test that flips is_frozen on the ProtocolParameters state and verifies open_position is rejected with the expected message. The freeze and unfreeze instructions themselves ship in the next issue in plan 5; this gate is what makes them effective once they ship."Notes for reviewer / future maintainers
ProtocolParametersdecode. If the decode itself fails (bad account input), the caller deserves the precise decode error, not a frozen-protocol error that wouldn't be the actual cause.force_write_account/ equivalent) rather than callingfreezebecausefreezeships in issue 02. This test deliberately scopes itself to one thing: that the gate landed here is real onceis_frozen = trueis observed in the on-chain state. Issue 03 will add tests that flip the flag via the realfreezeinstruction end-to-end."Protocol is frozen") shared across all three sites. If you ever want per-instruction differentiation (e.g. for telemetry), change all three at once and update the three#[should_panic(expected = ...)]attributes plus the integration test'scontains(...)assertion.stablecoin_corebeyond what Plan 1 already exposed (ProtocolParameterswithis_frozen: bool).Dependencies
Depends on: Plan 1 (#156,
is_frozenfield); Plan 3 (#173, the three target host functions).Blocks: #189, #190.