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
19 changes: 19 additions & 0 deletions docs/contracts/initialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,25 @@ pub fn get_grace_period_seconds(env: &Env) -> u64
```

Returns the grace period in seconds (defaults to 86,400).

---

#### `get_version`

```rust
pub fn get_version(env: &Env) -> u32
```

Returns the protocol version stored at initialization time, or the compiled-in
`PROTOCOL_VERSION` constant if the contract has not been initialized yet.

**Upgrade policy**

| Change type | Action |
|---|---|
| Patch (bug-fix, no storage-schema change) | No bump required |
| Minor (new fields, backward-compatible) | Bump recommended |
| Major (breaking storage changes, migration required) | Bump mandatory |

## Events

Expand Down
44 changes: 44 additions & 0 deletions quicklendx-contracts/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,23 @@ const WHITELIST_KEY: Symbol = symbol_short!("curr_wl");
/// Storage key for initialization lock (prevents concurrent initialization)
const INIT_LOCK_KEY: Symbol = symbol_short!("init_lck");

/// Storage key for the protocol version written at initialization time
const PROTOCOL_VERSION_KEY: Symbol = symbol_short!("proto_ver");

/// Current protocol version.
///
/// Increment this constant when deploying a new contract version.
/// The value is written to storage during `initialize` so that
/// `get_version` always reflects the version that was active when
/// the contract was first set up, even after a WASM upgrade that
/// bumps this constant.
///
/// # Upgrade policy
/// - Patch releases (bug-fixes, no storage-schema changes): no bump required.
/// - Minor releases (new fields, backward-compatible): bump recommended.
/// - Major releases (breaking storage changes, migration required): bump mandatory.
pub const PROTOCOL_VERSION: u32 = 1;

// Configuration constants with secure defaults
#[cfg(not(test))]
const DEFAULT_MIN_INVOICE_AMOUNT: i128 = 1_000_000; // 1 token (6 decimals)
Expand Down Expand Up @@ -241,6 +258,12 @@ impl ProtocolInitializer {
.set(&WHITELIST_KEY, &params.initial_currencies);
}

// ATOMIC: Persist the protocol version so get_version is consistent
// with the version that was active at initialization time.
env.storage()
.instance()
.set(&PROTOCOL_VERSION_KEY, &PROTOCOL_VERSION);

// COMMIT: Mark protocol as initialized (this is the atomic commit point)
env.storage()
.instance()
Expand Down Expand Up @@ -478,6 +501,27 @@ impl ProtocolInitializer {
// ============================================================================

impl ProtocolInitializer {
/// Get the protocol version stored at initialization time.
///
/// Returns the `PROTOCOL_VERSION` constant that was compiled into the
/// contract when `initialize` was first called. Falls back to the
/// current `PROTOCOL_VERSION` constant when the contract has not been
/// initialized yet (e.g. in a fresh test environment).
///
/// # Arguments
/// * `env` - The contract environment
///
/// # Returns
/// * `u32` - The stored protocol version, or `PROTOCOL_VERSION` if unset.
///
/// @notice Always consistent with the version active at init time.
pub fn get_version(env: &Env) -> u32 {
env.storage()
.instance()
.get(&PROTOCOL_VERSION_KEY)
.unwrap_or(PROTOCOL_VERSION)
}

/// Get the current fee in basis points.
///
/// # Arguments
Expand Down
60 changes: 60 additions & 0 deletions quicklendx-contracts/src/test_init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -845,4 +845,64 @@ mod test_init {
assert_eq!(fee_events.len(), 1, "Must have one fee event");
assert_eq!(treasury_events.len(), 1, "Must have one treasury event");
}

// ============================================================================
// 13. Version Consistency Tests
// ============================================================================

#[test]
fn test_get_version_before_init_returns_constant() {
let (_env, client) = setup();
assert_eq!(
client.get_version(),
crate::init::PROTOCOL_VERSION,
"get_version must return PROTOCOL_VERSION constant before init"
);
}

#[test]
fn test_get_version_after_init_matches_constant() {
let (_env, client, _params) = setup_initialized();
assert_eq!(
client.get_version(),
crate::init::PROTOCOL_VERSION,
"get_version must equal PROTOCOL_VERSION after init"
);
}

#[test]
fn test_get_version_stored_in_instance_storage() {
let (env, _client, _params) = setup_initialized();
assert_eq!(
crate::init::ProtocolInitializer::get_version(&env),
crate::init::PROTOCOL_VERSION,
"Stored version must match PROTOCOL_VERSION"
);
}

#[test]
fn test_get_version_consistent_before_and_after_init() {
let (env, client) = setup();
let before = client.get_version();
let params = create_valid_params(&env);
client.initialize(&params);
let after = client.get_version();
assert_eq!(
before, after,
"Version must be the same before and after init"
);
}

#[test]
fn test_reinit_does_not_change_version() {
let (_env, client, params) = setup_initialized();
let v1 = client.get_version();
// Idempotent re-init with same params must not alter version
let _ = client.try_initialize(&params);
assert_eq!(
client.get_version(),
v1,
"Version must not change on idempotent re-init"
);
}
}
Loading