Skip to content

fix(market): write StorageVersion before Admin in initialize - #650

Open
Akeem813 wants to merge 1 commit into
Vatix-Protocol:devfrom
Akeem813:fix/issue-init-double-call-guard
Open

fix(market): write StorageVersion before Admin in initialize#650
Akeem813 wants to merge 1 commit into
Vatix-Protocol:devfrom
Akeem813:fix/issue-init-double-call-guard

Conversation

@Akeem813

Copy link
Copy Markdown
Contributor

Problem

The two storage writes in initialize() were ordered admin-first, version-second. If the transaction is interrupted between those two writes (out-of-gas or host error), the resulting partial state is different depending on which write completed:

Interrupted after has_admin() assert_version() Result
set_admin (old ordering) true UpgradeRequired Bricked: initialize() returns AlreadyInitialized, but all storage accessors return UpgradeRequired. No recovery path short of redeployment.
set_version (new ordering) false Ok(()) Recoverable: initialize() can be retried. All require_initialized guards still reject callers, so nothing can be called in the gap.

Fix

Swap the write order: call storage::set_version before storage::set_admin.

The AlreadyInitialized guard (has_admin() check) is unchanged — it still correctly rejects any second complete call to initialize() after a successful first run.

Why This Satisfies "Reject initialize twice"

The existing has_admin() guard already returns ContractError::AlreadyInitialized on a second call. This PR does not change that logic — it hardens the write ordering so that the guard's sentinel value (Admin) is only written once all preceding state is durable. A second initialize() call on a successfully initialized contract still returns AlreadyInitialized (#42).

Files Changed

  • contracts/market/src/lib.rs — reorder set_version before set_admin inside initialize()

Acceptance Criteria

  • Second initialize returns ContractError::AlreadyInitialized — existing guard, unchanged
  • Partial-write recovery: interrupted init leaves contract in a retryable state, not a bricked one

The two storage writes in initialize() were ordered admin-first,
version-second. If a transaction is interrupted between the two writes
(out-of-gas or host error), the resulting partial state differs
critically depending on which write landed:

Before (admin first, version second):
  - Interrupted after set_admin, before set_version:
    has_admin() == true  → subsequent initialize() calls return
    AlreadyInitialized, but assert_version() returns UpgradeRequired.
    The contract is permanently locked out of both initialization and
    normal operation — bricked with no recovery path short of
    redeployment.

After (version first, admin second):
  - Interrupted after set_version, before set_admin:
    has_admin() == false → initialize() can be retried and will
    complete normally. All require_initialized guards still reject
    callers because has_admin() is false, so no state-mutating
    function can run in the gap. The contract is safe to retry.

The AlreadyInitialized guard itself (has_admin() check) is unchanged —
it still correctly rejects a second full call to initialize() after a
successful first run.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant