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
3 changes: 3 additions & 0 deletions contracts/predictify-hybrid/src/audit_trail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub struct AuditRecord {
pub timestamp: u64,
pub details: Map<Symbol, String>,
pub prev_record_hash: BytesN<32>,
pub override_nonce: Option<u64>,
}

/// Head of the audit trail, tracking the latest state.
Expand All @@ -83,6 +84,7 @@ impl AuditTrailManager {
action: AuditAction,
actor: Address,
details: Map<Symbol, String>,
override_nonce: Option<u64>,
) -> u64 {
let mut head: AuditTrailHead = env
.storage()
Expand All @@ -102,6 +104,7 @@ impl AuditTrailManager {
timestamp: env.ledger().timestamp(),
details,
prev_record_hash: head.latest_hash.clone(),
override_nonce: override_nonce,
};

// Use a tuple key for distinct storage namespace (Symbol, index)
Expand Down
6 changes: 6 additions & 0 deletions contracts/predictify-hybrid/src/err.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ pub enum Error {
GasBudgetExceeded = 417,
/// Admin address has not been set. Contract initialization is incomplete.
AdminNotSet = 418,
/// Admin override verification failed due to replay attack - nonce <= stored nonce.
ReplayedOverride = 419,

// ===== METADATA LENGTH LIMIT ERRORS (420-434) =====
/// Market question exceeds maximum allowed length.
Expand Down Expand Up @@ -176,6 +178,10 @@ pub enum Error {
/// Tag string is shorter than the minimum allowed length (non-empty tags only).
TagTooShort = 437,

// ===== VALIDATION ERRORS (435-437) =====
/// Market ID already exists in the registry. Cannot create duplicate market IDs.
DuplicateMarketId = 435,

// ===== CIRCUIT BREAKER ERRORS ====="
/// Circuit breaker has not been initialized. Initialize before use.
CBNotInitialized = 500,
Expand Down
23 changes: 23 additions & 0 deletions contracts/predictify-hybrid/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2880,6 +2880,7 @@ impl PredictifyHybrid {
market_id: Symbol,
outcome: String,
reason: String,
provided_nonce: u64,
) -> Result<(), Error> {
Self::require_primary_admin(&env, &admin)?;

Expand All @@ -2903,6 +2904,27 @@ impl PredictifyHybrid {
markets::MarketStateManager::update_market(&env, &market_id, &market);

// Append an immutable audit record
// Validate and store the admin override nonce for replay protection
let key = DataKey::AdminOverrideNonce(admin.clone());
let mut stored_nonce: u64 = env
.storage()
.persistent()
.get(&key)
.unwrap_or(0);

if provided_nonce <= stored_nonce {
return Err(Error::ReplayedOverride);
}

// Update the nonce for this admin
env.storage().persistent().set(&key, &provided_nonce);
env.storage().persistent().extend_ttl(
&key,
env.storage().max_ttl(),
env.storage().max_ttl(),
);

// Append an immutable audit record with the nonce for replay protection
let mut details = Map::new(&env);
details.set(Symbol::new(&env, "old_result"), old_result.clone());
details.set(Symbol::new(&env, "new_result"), outcome.clone());
Expand All @@ -2912,6 +2934,7 @@ impl PredictifyHybrid {
AuditAction::OracleVerificationOverride,
admin.clone(),
details,
Some(provided_nonce),
);

// Emit the dedicated override event for off-chain monitors
Expand Down
Loading
Loading