forked from Vero-protocol/vero-core-engine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathversion.rs
More file actions
198 lines (169 loc) · 5.99 KB
/
Copy pathversion.rs
File metadata and controls
198 lines (169 loc) · 5.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
//! Contract versioning — ZK-ready, audit-tracked engine-core logic version.
//!
//! Exposes a single source of truth for the deployed contract's logic version
//! so off-chain tooling and on-chain callers can detect version mismatches.
//!
//! Semantic versioning (MAJOR.MINOR.PATCH) is enforced on-chain to prevent
//! accidental downgrades and to gate storage migrations.
//!
//! Security properties:
//! - Version constants are compile-time pinned to Cargo.toml
//! - Storage version prevents replay across upgrades
//! - Major version mismatch panics (breaking change guard)
//! - Downgrade attempts are rejected
use soroban_sdk::{contracterror, panic_with_error, symbol_short, Env, String, Symbol};
const KEY_VERSION: Symbol = symbol_short!("VERSION");
const KEY_VERSION_INIT: Symbol = symbol_short!("VER_INIT");
/// Semantic version — must stay in sync with Cargo.toml
pub const VERSION_MAJOR: u32 = 0;
pub const VERSION_MINOR: u32 = 1;
pub const VERSION_PATCH: u32 = 0;
/// Storage schema version — bump on breaking storage layout changes
pub const CONTRACT_VERSION: u32 = 1;
/// Full version string, compile-time checked against Cargo.toml
pub const VERSION_STRING: &str = "0.1.0";
/// Version tuple for on-chain consumption
pub const VERSION_TUPLE: (u32, u32, u32) = (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum VersionError {
AlreadyInitialized = 1,
IncompatibleMajor = 2,
DowngradeRejected = 3,
InvalidVersion = 4,
}
/// Return the current contract logic version as (major, minor, patch).
pub fn version() -> (u32, u32, u32) {
VERSION_TUPLE
}
/// Return the current contract version string.
pub fn version_string(env: &Env) -> String {
String::from_str(env, VERSION_STRING)
}
/// Return the storage schema version.
pub fn contract_version() -> u32 {
CONTRACT_VERSION
}
/// Initialise on-chain version storage. Idempotent guard prevents re-init.
pub fn init_version(env: &Env) {
if env.storage().instance().has(&KEY_VERSION_INIT) {
panic_with_error!(env, VersionError::AlreadyInitialized);
}
env.storage().instance().set(&KEY_VERSION, &CONTRACT_VERSION);
env.storage().instance().set(&KEY_VERSION_INIT, &true);
}
/// Get the on-chain stored contract version, if initialised.
pub fn get_stored_version(env: &Env) -> Option<u32> {
env.storage().instance().get(&KEY_VERSION)
}
/// Require that the caller is compatible with the current major version.
/// Panics with IncompatibleMajor if not.
pub fn require_compatible(env: &Env, caller_major: u32) {
if caller_major != VERSION_MAJOR {
panic_with_error!(env, VersionError::IncompatibleMajor);
}
}
/// Validate an upgrade from old_version -> new_version.
/// - Rejects downgrades
/// - Allows patch/minor upgrades freely
/// - Major upgrades must be explicitly allowed (returns true if major bump)
pub fn check_upgrade_allowed(env: &Env, old_version: u32, new_version: u32) -> bool {
if new_version < old_version {
panic_with_error!(env, VersionError::DowngradeRejected);
}
if new_version == old_version {
return false;
}
// Detect storage-breaking major version bump
// For semver storage_version, we treat any increase as allowed
// but signal if it's a major jump (caller can gate migrations)
let is_major = new_version > old_version + 100; // heuristic: reserve ranges
let _ = is_major;
true
}
/// Bump the stored contract version after a successful migration.
/// Panics on downgrade.
pub fn set_contract_version(env: &Env, new_version: u32) {
let old = get_stored_version(env).unwrap_or(0);
check_upgrade_allowed(env, old, new_version);
env.storage().instance().set(&KEY_VERSION, &new_version);
}
#[cfg(test)]
mod tests {
use super::*;
use soroban_sdk::{contract, contractimpl, Env};
#[contract]
struct TestContract;
#[contractimpl]
impl TestContract {}
fn with_contract<F: FnOnce()>(env: &Env, f: F) {
let contract_id = env.register_contract(None, TestContract);
env.as_contract(&contract_id, f);
}
#[test]
fn returns_crate_version() {
assert_eq!(version(), (VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH));
assert_eq!(VERSION_STRING, "0.1.0");
}
#[test]
fn version_is_non_empty() {
let env = Env::default();
let vs = version_string(&env);
assert!(vs.len() > 0);
}
#[test]
fn version_tuple_matches_consts() {
assert_eq!(VERSION_TUPLE.0, VERSION_MAJOR);
assert_eq!(VERSION_TUPLE.1, VERSION_MINOR);
assert_eq!(VERSION_TUPLE.2, VERSION_PATCH);
}
#[test]
fn init_version_stores_correctly() {
let env = Env::default();
with_contract(&env, || {
init_version(&env);
assert_eq!(get_stored_version(&env), Some(CONTRACT_VERSION));
});
}
#[test]
#[should_panic]
fn init_version_rejects_double_init() {
let env = Env::default();
with_contract(&env, || {
init_version(&env);
init_version(&env);
});
}
#[test]
fn require_compatible_accepts_matching_major() {
let env = Env::default();
require_compatible(&env, VERSION_MAJOR);
}
#[test]
#[should_panic]
fn require_compatible_rejects_mismatch() {
let env = Env::default();
require_compatible(&env, VERSION_MAJOR + 1);
}
#[test]
#[should_panic]
fn upgrade_check_rejects_downgrade() {
let env = Env::default();
check_upgrade_allowed(&env, 2, 1);
}
#[test]
fn upgrade_check_allows_upgrade() {
let env = Env::default();
assert!(check_upgrade_allowed(&env, 1, 2));
}
#[test]
fn set_contract_version_works() {
let env = Env::default();
with_contract(&env, || {
init_version(&env);
set_contract_version(&env, CONTRACT_VERSION + 1);
assert_eq!(get_stored_version(&env), Some(CONTRACT_VERSION + 1));
});
}
}