Skip to content

feat: enforce semver forward-only contract upgrades#488

Open
yunus-dev-codecrafter wants to merge 1 commit into
RevoraOrg:masterfrom
fikrah-Tech:feat/semver-upgrade-guard
Open

feat: enforce semver forward-only contract upgrades#488
yunus-dev-codecrafter wants to merge 1 commit into
RevoraOrg:masterfrom
fikrah-Tech:feat/semver-upgrade-guard

Conversation

@yunus-dev-codecrafter

Copy link
Copy Markdown
Contributor

closes #483

feat: enforce semver forward-only contract upgrades

Summary

Replace the flat CONTRACT_VERSION: u32 with a semver (MAJOR, MINOR, PATCH) triple and add a migrate_storage entrypoint that rejects downgrades and no-op migrations.

Motivation

Previously there was no programmatic check that an upgrade does not regress major (or minor/patch) version. A deployer could accidentally migrate to an older version, corrupting storage compatibility expectations.

Changes

src/lib.rs

  • CONTRACT_VERSION (line 368): u32 = 23(u32, u32, u32) = (1, 0, 23). The triple avoids string parsing and makes semver ordering trivial (lexicographic tuple comparison).
  • assert_semver_forward(from, to) (line 384): Pure helper that returns:
    • Err(AlreadyAtTargetVersion) when versions are equal
    • Err(MigrationDowngradeNotAllowed) when any component regresses
    • Ok(()) for valid forward upgrades
  • migrate_storage (line 5274): New admin-only entrypoint that:
    1. Verifies the contract is initialized, caller is admin, and contract is not frozen
    2. Reads the stored DeployedVersion (defaults to CONTRACT_VERSION if absent)
    3. Calls assert_semver_forward(stored, target)
    4. On success, persists the new version and emits (symbol_short!("migrate"), (from, to)) event
  • get_version (line 5256): Return type changed to (u32, u32, u32).

src/test_storage_layout_version.rs

Added 20 new tests covering:

Category Tests
assert_semver_forward unit tests 6 tests: major/minor/patch upgrade OK, noop rejected, major/minor/patch downgrade rejected
migrate_storage integration 11 tests: every forward path succeeds, every downgrade path rejected, noop rejected, frozen/uninitialized/non-admin rejected, sequential upgrades, event emission

src/structured_error_tests.rs

Updated the const assertion for the new triple format (CONTRACT_VERSION.0 >= 1).

Removed

src/test_security_doc_sync.rs — orphan test file referencing a nonexistent get_security_doc_sync function (never compiled).

Error reuse

No new wire-level error discriminants were added. The helper reuses:

Condition Error
to == from AlreadyAtTargetVersion (32)
to < from MigrationDowngradeNotAllowed (33)

Security

  • migrate_storage requires admin.require_auth() via Soroban host auth
  • The require_not_frozen check prevents migration on a frozen contract
  • Version comparison is pure and deterministic (no storage reads during comparison)

Testing

  • assert_semver_forward is tested as a pure function with all 6 branch outcomes
  • migrate_storage is integration-tested with scenarios covering initialization state, auth, frozen state, and every upgrade/downgrade direction
  • Existing get_version and storage_layout_version tests continue to pass

@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@yunus-dev-codecrafter Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

Add CONTRACT_VERSION semantic-versioning check rejecting upgrade if breaking-major regression detected

1 participant