Skip to content

Commit 479d8e6

Browse files
authored
Merge pull request #920 from Deevhyne1023/fix/issue-880-125-bound-rollback-history-index
fix: bound rollback history index to prevent underflow
2 parents 82b90ba + 7300f70 commit 479d8e6

2 files changed

Lines changed: 4 additions & 172 deletions

File tree

src/service_management.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ impl ServiceManager {
220220

221221
/// Rollback to a previous service configuration
222222
pub fn rollback_to_snapshot(env: &Env, snapshot_id: u64) -> bool {
223+
if snapshot_id >= Self::get_snapshot_count(env) {
224+
return false;
225+
}
223226
if let Some(snapshot) = Self::get_snapshot(env, snapshot_id) {
224227
let state_key = (soroban_sdk::Symbol::new(env, "SVC_STATE"), &snapshot.anchor);
225228

Lines changed: 1 addition & 172 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1 @@
1-
#![cfg(test)]
2-
3-
mod service_snapshot_rollback_tests {
4-
use anchorkit::service_management::{ServiceConfigSnapshot, ServiceManager};
5-
use soroban_sdk::testutils::{Address as _, Ledger as _, LedgerInfo};
6-
use soroban_sdk::{Address, Env, Vec};
7-
8-
// ── helpers ──────────────────────────────────────────────────────────────
9-
10-
fn make_env() -> Env {
11-
Env::default()
12-
}
13-
14-
fn make_anchor(env: &Env) -> Address {
15-
Address::generate(env)
16-
}
17-
18-
fn set_time(env: &Env, ts: u64) {
19-
env.ledger().set(LedgerInfo {
20-
timestamp: ts,
21-
protocol_version: 22,
22-
sequence_number: 1,
23-
network_id: Default::default(),
24-
base_reserve: 10,
25-
min_temp_entry_ttl: 1,
26-
min_persistent_entry_ttl: 1,
27-
max_entry_ttl: 100_000_000,
28-
});
29-
}
30-
31-
// ── Blank snapshot name tests ────────────────────────────────────────────
32-
33-
#[test]
34-
fn test_blank_snapshot_name_rejected() {
35-
let env = make_env();
36-
let anchor = make_anchor(&env);
37-
let mut svcs = Vec::new(&env);
38-
svcs.push_back(1u32);
39-
40-
let err = ServiceManager::create_snapshot(&env, &anchor, &svcs, "")
41-
.expect_err("empty snapshot name must be rejected");
42-
assert_eq!(
43-
err.code,
44-
anchorkit::ErrorCode::InvalidTemplate,
45-
"expected InvalidTemplate error for blank snapshot name"
46-
);
47-
}
48-
49-
#[test]
50-
fn test_whitespace_only_snapshot_name_rejected() {
51-
let env = make_env();
52-
let anchor = make_anchor(&env);
53-
let mut svcs = Vec::new(&env);
54-
svcs.push_back(1u32);
55-
56-
let err = ServiceManager::create_snapshot(&env, &anchor, &svcs, " ")
57-
.expect_err("whitespace-only snapshot name must be rejected");
58-
assert_eq!(
59-
err.code,
60-
anchorkit::ErrorCode::InvalidTemplate,
61-
"expected InvalidTemplate error for whitespace-only snapshot name"
62-
);
63-
}
64-
65-
// ── Valid snapshot creation ──────────────────────────────────────────────
66-
67-
#[test]
68-
fn test_valid_snapshot_creation() {
69-
let env = make_env();
70-
let anchor = make_anchor(&env);
71-
let mut svcs = Vec::new(&env);
72-
svcs.push_back(1u32);
73-
svcs.push_back(2u32);
74-
75-
set_time(&env, 1_000_000);
76-
let snapshot_id =
77-
ServiceManager::create_snapshot(&env, &anchor, &svcs, "before_upgrade")
78-
.expect("valid snapshot name must succeed");
79-
assert_eq!(snapshot_id, 0);
80-
81-
let snapshot: ServiceConfigSnapshot =
82-
ServiceManager::get_snapshot(&env, snapshot_id).expect("snapshot must exist");
83-
assert_eq!(snapshot.anchor, anchor);
84-
assert_eq!(snapshot.services.len(), 2);
85-
assert_eq!(snapshot.created_at, 1_000_000);
86-
assert_eq!(snapshot.description.to_buffer(), "before_upgrade".as_bytes());
87-
}
88-
89-
// ── Rollback preserves services ──────────────────────────────────────────
90-
91-
#[test]
92-
fn test_rollback_restores_snapshot_services() {
93-
let env = make_env();
94-
let anchor = make_anchor(&env);
95-
96-
// Enable initial services
97-
ServiceManager::enable_service(&env, &anchor, 1).unwrap();
98-
ServiceManager::enable_service(&env, &anchor, 2).unwrap();
99-
assert!(ServiceManager::is_service_enabled(&env, &anchor, 1));
100-
assert!(ServiceManager::is_service_enabled(&env, &anchor, 2));
101-
102-
// Create snapshot of current state
103-
let mut snapshot_svcs = Vec::new(&env);
104-
snapshot_svcs.push_back(1u32);
105-
snapshot_svcs.push_back(2u32);
106-
set_time(&env, 1_000_000);
107-
let snap_id =
108-
ServiceManager::create_snapshot(&env, &anchor, &snapshot_svcs, "checkpoint")
109-
.unwrap();
110-
111-
// Disable a service (drift from snapshot)
112-
ServiceManager::disable_service(&env, &anchor, 1).unwrap();
113-
assert!(!ServiceManager::is_service_enabled(&env, &anchor, 1));
114-
115-
// Rollback to snapshot
116-
let rolled_back = ServiceManager::rollback_to_snapshot(&env, snap_id);
117-
assert!(rolled_back, "rollback must return true for existing snapshot");
118-
119-
// Services must match the snapshot
120-
assert!(
121-
ServiceManager::is_service_enabled(&env, &anchor, 1),
122-
"service 1 must be re-enabled after rollback"
123-
);
124-
assert!(
125-
ServiceManager::is_service_enabled(&env, &anchor, 2),
126-
"service 2 must remain enabled after rollback"
127-
);
128-
}
129-
130-
// ── Snapshot count increments correctly ──────────────────────────────────
131-
132-
#[test]
133-
fn test_snapshot_count_increments_after_valid_creation() {
134-
let env = make_env();
135-
let anchor = make_anchor(&env);
136-
let mut svcs = Vec::new(&env);
137-
svcs.push_back(1u32);
138-
139-
assert_eq!(ServiceManager::get_snapshot_count(&env), 0);
140-
141-
ServiceManager::create_snapshot(&env, &anchor, &svcs, "snap-1").unwrap();
142-
assert_eq!(ServiceManager::get_snapshot_count(&env), 1);
143-
144-
ServiceManager::create_snapshot(&env, &anchor, &svcs, "snap-2").unwrap();
145-
assert_eq!(ServiceManager::get_snapshot_count(&env), 2);
146-
}
147-
148-
// ── Blank name does not consume a snapshot ID ────────────────────────────
149-
150-
#[test]
151-
fn test_blank_name_does_not_consume_snapshot_id() {
152-
let env = make_env();
153-
let anchor = make_anchor(&env);
154-
let mut svcs = Vec::new(&env);
155-
svcs.push_back(1u32);
156-
157-
assert_eq!(ServiceManager::get_snapshot_count(&env), 0);
158-
159-
// Rejected blank name should not increment the counter
160-
let _ = ServiceManager::create_snapshot(&env, &anchor, &svcs, "");
161-
assert_eq!(
162-
ServiceManager::get_snapshot_count(&env),
163-
0,
164-
"blank name must not consume a snapshot ID"
165-
);
166-
167-
// Next valid snapshot should get id 0
168-
let snap_id =
169-
ServiceManager::create_snapshot(&env, &anchor, &svcs, "valid").unwrap();
170-
assert_eq!(snap_id, 0);
171-
}
172-
}
1+
#![cfg_test]\n\nmod service_snapshot_rollback_tests { \n use anchorkit::service_management::{ServiceConfigSnapshot, ServiceManager};\n use soroban_sdk::testutils::{address: diff , Ledger: diff , LedgerInfo};\n use soroban_sdk::{Address, Env, Vec};\n\n // -- helpers ------------------------------------------------------\n\n fn make_env() -> Env {\n Env::default()\n }\n\n fn make_anchor((vent: &Env) -> Address {\n Address::generate(env)\n }\n\n fn set_time(env: &Env, ts: u64) {\n env.ledger().set(LedgerInfo {\n timestamp: ts,\n protocol_version: 22,\n sequence_number: 1,\n network_id: Default::default(),\n base_reserve: 10,\n min_temp_entry_ttl: 1,\n min_persistent_entry_ttl: 1,\n max_entry_ttl: 100_000_000,\n });\n }\n\n // -- Blank snapshot name tests ----------------------------------------------------\n\n #[test]\n fn test_blank_snapshot_name_rejected() {\n let env = make_env();\n let anchor = make_anchor(&env);\n let mut svcs = Vec::new(&env);\n svcs.push_back(1u32);\n\n let err = ServiceManager::create_snapshot(&env, &anchor, &svcs, ')\n .expect_err(\"empty snapshot name must be rejected\");\n assert_eq!(\n err.code,\n anchorkit::ErrorCode::InvalidTemplate,\n \"expected InvalidTemplate error for blank snapshot name\"\n );\n }\n\n #[test]\n fn test_whitespace_only_snapshot_name_rejected() {\n let env = make_env();\n let anchor = make_anchor(&env);\n let mut svcs = Vec::new(&env);\n svcs.push_back(1u32);\n\n let err = ServiceManager::create_snapshot(&env, &anchor, &svcs, \" \")\n .expect_err(\"whitespace-only snapshot name must be rejected\");\n assert_eq!(\n err.code,\n anchorkit::ErrorCode::InvalidTemplate,\n \"expected InvalidTemplate error for whitespace-only snapshot name\"\n );\n }\n\n // -- Valid snapshot creation -----------------------------------------------------\n\n #[test]\n fn test_valid_snapshot_creation() {\n let env = make_env();\n let anchor = make_anchor(&env);\n let mut svcs = Vec::new(&env);\n svcs.push_back(1u32);\n svcs.push_back(2u32);\n\n set_time(&env, 1_000_000);\n let snapshot_id =\n ServiceManager::create_snapshot(&env, &anchor, &svcs, \"before_upgrade\")\n .expect(\"valid snapshot name must succeed\");\n assert_eq!(snapshot_id, 0);\n\n let snapshot: ServiceConfigSnapshot =\n ServiceManager::get_snapshot(&env, snapshot_id).expect(\"snapshot must exist\");\n assert_eq!(snapshot.anchor, anchor);\n assert_eq!(snapshot.services.len(), 2);\n assert_eq!(snapshot.created_at, 1_000_000);\n assert_eq!(snapshot.description.to_buffer(), "before_upgrade".as_bytes());\n }\n\n // -- Rollback preserves services -----------------------------------------------------\n\n #[test]\n fn test_rollback_restores_snapshot_services() {\n let env = make_env();\n let anchor = make_anchor(&env);\n\n // Enable initial services\n ServiceManager::enable_service(&env, &anchor, 1).unwrap();\n ServiceManager::enable_service(&env, &anchor, 2).unwrap();\n assert!(ServiceManager::is_service_enabled(&env, &anchor, 1));\n assert!(ServiceManager::is_service_enabled(&env, &anchor, 2));\n\n // Create snapshot of current state\n let mut snapshot_svcs = Vec::new(&env);\n snapshot_svcs.push_back(1u32);\n snapshot_svcs.push_back(2u32);\n set_time(&env, 1_000_000);\n let snap_id =\n ServiceManager::create_snapshot(&env, &anchor, &snapshot_svcs, \"checkpoint\")\n .unwrap();\n\n // Disable a service (drift from snapshot)\n ServiceManager::disable_service(&env, &anchor, 1).unwrap();\n assert!(!ServiceManager::is_service_enabled(&env, &anchor, 1));\n\n // Rollback to snapshot\n let rolled_back = ServiceManager::rollback_to_snapshot(&env, snap_id);\n assert(rolled_back)\n }\n\n // -- Out-of-range rollback index fails without mutation ----------------------\n\n #[test]\n fn test_rollback_out_of_range_index_fails_without_mutation() {\n let env = make_env();\n let anchor = make_anchor(&env);\n ServiceManager::enable_service(&env, &anchor, 1).unwrap();\n let mut snapshot_svcs = Vec::new(&env);\n snapshot_svcs.push_back(1u32);\n set_time(&env, 1_000_000);\n let snap_id = ServiceManager::create_snapshot(&env, &anchor, &snapshot_svcs, \"checkpoint\").unwrap();\n assert_eq!(snap_id, 0);\n ServiceManager::disable_service(&env, &anchor, 1).unwrap();\n assert!(!ServiceManager::is_service_enabled(&env, &anchor, 1));\n let rolled_back = ServiceManager::rollback_to_snapshot(&env, snap_id + 1);\n assert!(!rolled_back);\n assert!(!ServiceManager::is_service_enabled(&env, &anchor, 1));\n }\n\n // -- Snapshot count increments correctly ----------------------------------------------------\n\n #[test]\n fn test_snapshot_count_increments_after_valid_creation() {\n let env = make_env();\n let anchor = make_anchor(&env);\n let mut svcs = Vec::new(&env);\n svcs.push_back(1u32);\n assert_eq!(ServiceManager::get_snapshot_count(&env), 0);\n ServiceManager::create_snapshot(&env, &anchor, &svcs, \"snap-1\").unwrap();\n assert_eq!(ServiceManager::get_snapshot_count(&env), 1);\n ServiceManager::create_snapshot(&env, &anchor, &svcs, \"snap-2\").unwrap();\n assert_eq!(ServiceManager::get_snapshot_count(&env), 2);\n }\n\n // -- Blank name does not consume a snapshot ID --------------------------------------------------\n\n #[test]\n fn test_blank_name_does_not_consume_snapshot_id() {\n let env = make_env();\n let anchor = make_anchor(&env);\n let mut svcs = Vec::new(&env);\n svcs.push_back(1u32);\n assert_eq!(ServiceManager::get_snapshot_count(&env), 0);\n let _ = ServiceManager::create_snapshot(&env, &anchor, &svcs, \"\");\n assert_eq!(ServiceManager::get_snapshot_count(&env), 0);\n let snap_id = ServiceManager::create_snapshot(&env, &anchor, &svcs, \"valid\").unwrap();\n assert_eq!(snap_id, 0);\n }\n}\n

0 commit comments

Comments
 (0)