Skip to content
Merged
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
48 changes: 48 additions & 0 deletions contracts/credential-nft/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ impl CredentialNft {
description: Option<Symbol>,
issuer_name: Option<Symbol>,
) {
Self::require_not_paused(&env);
let admin: Address = env
.storage()
.persistent()
Expand Down Expand Up @@ -496,6 +497,7 @@ impl CredentialNft {
/// * If the caller is not the current admin
/// * If `new_admin` is the zero address
pub fn transfer_admin(env: Env, new_admin: Address) {
Self::require_not_paused(&env);
let admin: Address = env
.storage()
.persistent()
Expand Down Expand Up @@ -622,6 +624,51 @@ impl CredentialNft {
write_entry(&env, &CredentialDataKey::AdminTransferDelay, &delay_seconds);
}

// ── Emergency Pause (#475) ───────────────────────────────────────────────

/// Check if the contract is paused.
fn is_paused(env: &Env) -> bool {
env.storage()
.persistent()
.get(&CredentialDataKey::Paused)
.unwrap_or(false)
}

/// Require that the contract is not paused, panicking otherwise.
fn require_not_paused(env: &Env) {
if Self::is_paused(env) {
panic!("contract is paused");
}
}

/// Pause all state-changing operations. Admin only (#475).
pub fn emergency_pause(env: Env) {
let admin: Address = env
.storage()
.persistent()
.get(&CredentialDataKey::Admin)
.expect("not initialized");
admin.require_auth();
if Self::is_paused(&env) {
panic!("already paused");
}
write_entry(&env, &CredentialDataKey::Paused, &true);
}

/// Unpause all operations. Admin only (#475).
pub fn unpause(env: Env) {
let admin: Address = env
.storage()
.persistent()
.get(&CredentialDataKey::Admin)
.expect("not initialized");
admin.require_auth();
if !Self::is_paused(&env) {
panic!("not paused");
}
write_entry(&env, &CredentialDataKey::Paused, &false);
}

/// Reject transfer of a credential.
///
/// Credentials are soulbound (non-transferable) and permanently bound to the
Expand Down Expand Up @@ -664,6 +711,7 @@ impl CredentialNft {
/// # Returns
/// The generated certificate URI symbol.
pub fn generate_certificate(env: Env, learner: Address, course_id: Symbol) -> Symbol {
Self::require_not_paused(&env);
learner.require_auth();

let cert_key = CredentialDataKey::CertificateURI(learner.clone(), course_id.clone());
Expand Down
4 changes: 3 additions & 1 deletion scripts/upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,9 @@ else
fi

echo "$UPDATED" | jq empty 2>/dev/null
if [ $? -eq 0 ]; then
# Issue #477: Fixed JSON validation to work with set -e
# Use conditional that doesn't exit immediately on jq failure
if echo "$UPDATED" | jq empty 2>/dev/null; then
echo "$UPDATED" > "$DEPLOY_FILE"
echo " Deployment file updated: $DEPLOY_FILE"
else
Expand Down
Loading