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
70 changes: 70 additions & 0 deletions apexchainx_calculator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,71 @@ impl SLACalculatorContract {
Ok(())
}

// Initialise any storage keys that may be missing from older schema
// versions. This is intentionally conservative: only set a value when
// the key is absent so migration is idempotent and does not overwrite
// existing state.
fn init_missing_storage_defaults(env: &Env) {
let inst = env.storage().instance();

if !inst.has(&PAUSED_KEY) {
inst.set(&PAUSED_KEY, &false);
}

if !inst.has(&STATS_KEY) {
inst.set(
&STATS_KEY,
&SLAStats {
total_calculations: 0,
total_violations: 0,
total_rewards: 0,
total_penalties: 0,
},
);
}

if !inst.has(&HISTORY_KEY) {
inst.set(&HISTORY_KEY, &Vec::<SLAResult>::new(env));
}

if !inst.has(&CONFIG_KEY) {
let mut configs = Map::<Symbol, SLAConfig>::new(env);
configs.set(
symbol_short!("critical"),
SLAConfig {
threshold_minutes: 15,
penalty_per_minute: 100,
reward_base: 750,
},
);
configs.set(
symbol_short!("high"),
SLAConfig {
threshold_minutes: 30,
penalty_per_minute: 50,
reward_base: 750,
},
);
configs.set(
symbol_short!("medium"),
SLAConfig {
threshold_minutes: 60,
penalty_per_minute: 25,
reward_base: 750,
},
);
configs.set(
symbol_short!("low"),
SLAConfig {
threshold_minutes: 120,
penalty_per_minute: 10,
reward_base: 600,
},
);
inst.set(&CONFIG_KEY, &configs);
}
}

// -------------------------------------------------------------------
// #61 – Storage migration harness
// -------------------------------------------------------------------
Expand Down Expand Up @@ -593,6 +658,11 @@ impl SLACalculatorContract {

// v0 → v1: stamp the version; all other fields were set by initialize
if current == 0 {
// Ensure any storage keys that might be missing from older
// deployments are initialised to deterministic defaults before
// we mark the storage version as migrated. This codifies the
// contract: migration arms must initialise newly-added keys.
Self::init_missing_storage_defaults(&env);
env.storage().instance().set(&STORAGE_VERSION_KEY, &1u32);
current = 1;
}
Expand Down
33 changes: 33 additions & 0 deletions apexchainx_calculator/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3200,6 +3200,39 @@ fn test_get_migration_state_after_migrate_shows_no_migration_needed() {
assert!(!info.needs_migration);
}

#[test]
fn test_migrate_initialises_missing_fields() {
// Simulate a frozen older snapshot that lacks keys added in later
// schemas, then run migrate and verify deterministic defaults are set.
let env = Env::default();
let cid = env.register_contract(None, SLACalculatorContract);
let client = SLACalculatorContractClient::new(&env, &cid);
let admin = soroban_sdk::Address::generate(&env);
let op = soroban_sdk::Address::generate(&env);
client.initialize(&admin, &op);

// Remove some keys to emulate an older schema state and force stored
// version to 0 so migrate will exercise the v0->v1 path.
env.as_contract(&cid, || {
env.storage().instance().remove(&CONFIG_KEY);
env.storage().instance().remove(&STATS_KEY);
env.storage().instance().set(&STORAGE_VERSION_KEY, &0u32);
});

// Run migration as admin
client.migrate(&admin);

// After migrate the keys should exist again and the stored version
// should match the binary's STORAGE_VERSION.
let version = client.get_storage_version();
assert_eq!(version, STORAGE_VERSION);

env.as_contract(&cid, || {
assert!(env.storage().instance().has(&CONFIG_KEY));
assert!(env.storage().instance().has(&STATS_KEY));
});
}

// ============================================================
// SC-011 – Latest result by outage (issue #131) – additional coverage
// ============================================================
Expand Down
Loading