Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

version_manager source pure

Package version\_manager provides a runtime version management system for dynamic implementation switching without da...

Readme View source

Version Manager

Runtime version management system for dynamic implementation switching without data migration.

Overview

Version Manager implements a Strategy Pattern-based system that enables hot-swapping between different versioned implementations of the same domain (e.g., v1, v2, v3) while maintaining a unified storage layer. This approach allows seamless upgrades without downtime or migration overhead.

Features

  • Zero-Downtime Upgrades: Switch implementations at runtime without service interruption
  • Unified Storage: All versions share a single KVStore owned by the domain (proxy) realm
  • Domain-Scoped Security: Only authorized packages within the domain path can register
  • Hot-Swapping: Instant version switching through dynamic strategy replacement
  • Secure by Design: Implementation realms cannot directly modify storage (see Storage Access Model below)

Pattern: Strategy + Plugin Architecture

Usage

Step 1: Define Domain Interface

1// protocol_fee/types.gno
2package protocol_fee
3
4type ProtocolFee interface {
5    SetFeeRatio(ratio uint64) error
6    GetFeeRatio() uint64
7}

Step 2: Create Version Manager

 1// protocol_fee/protocol_fee.gno
 2package protocol_fee
 3
 4import "gno.land/p/gnoswap/version_manager"
 5import "gno.land/p/gnoswap/store"
 6
 7var manager version_manager.VersionManager
 8
 9func init(cur realm) {
10    kvStore := store.NewKVStore(cur.Address())
11
12    manager = version_manager.NewVersionManager(
13        cur.PkgPath(),
14        kvStore,
15        // initializeDomainStoreFn carries the v2 interrealm marker (`_ int, rlm realm`):
16        // the leading 0 surfaces realm-threading at the call site.
17        func(_ int, rlm realm, kv store.KVStore) any {
18            return NewProtocolFeeStore(kv)
19        },
20    )
21}
22
23func GetManager() version_manager.VersionManager {
24    return manager
25}
26
27// RegisterInitializer is the crossing entry point each version package calls.
28// `cur` is the live crossing-frame realm token; it is threaded straight into the
29// version manager (the leading 0 is the v2 sentinel) so the manager can reject
30// spoofed/stale tokens via rlm.IsCurrent() and identify the caller via rlm.Previous().
31func RegisterInitializer(cur realm, initializer func(_ int, rlm realm, store any) any) {
32    if err := manager.RegisterInitializer(0, cur, initializer); err != nil {
33        panic(err)
34    }
35}
36
37// UpgradeImpl switches the active version. Authorization (admin / governance) is
38// enforced here in the /r/ realm; version_manager only rejects spoofed tokens.
39func UpgradeImpl(cur realm, packagePath string) {
40    if err := manager.ChangeImplementation(0, cur, packagePath); err != nil {
41        panic(err)
42    }
43}

Step 3: Implement Versions

 1// protocol_fee/v1/v1.gno
 2package v1
 3
 4import "gno.land/r/gnoswap/protocol_fee"
 5
 6type protocolFeeV1 struct {
 7    store any
 8}
 9
10func init(cur realm) {
11    // Register this version during package initialization.
12    // `cross(cur)` invokes the domain's crossing entry point, which threads the
13    // live realm token into the version manager.
14    protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
15        return &protocolFeeV1{store: store}
16    })
17}
18
19func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error {
20    // v1 implementation
21}
22
23func (pf *protocolFeeV1) GetFeeRatio() uint64 {
24    // v1 implementation
25}
 1// protocol_fee/v2/v2.gno
 2package v2
 3
 4type protocolFeeV2 struct {
 5    store any
 6}
 7
 8func init(cur realm) {
 9    // Register v2 — inactive until explicitly activated.
10    protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
11        return &protocolFeeV2{store: store}
12    })
13}
14
15func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error {
16    // v2 improved implementation
17}
18
19func (pf *protocolFeeV2) GetFeeRatio() uint64 {
20    // v2 improved implementation
21}

Step 4: Use Active Implementation

 1// client code
 2import "gno.land/r/gnoswap/protocol_fee"
 3
 4func UseFee() {
 5    manager := protocol_fee.GetManager()
 6    impl := manager.GetCurrentImplementation().(protocol_fee.ProtocolFee)
 7
 8    ratio := impl.GetFeeRatio()
 9    // Use the active version's implementation
10}

Step 5: Switch Versions at Runtime

1// governance or admin entry point
2func UpgradeToV2(cur realm) {
3    // Hot-swap to v2 — zero downtime. `cross(cur)` enters UpgradeImpl's crossing
4    // frame; UpgradeImpl threads the realm token into the version manager.
5    protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")
6}

Workflow

Registration Flow

1. Domain package initializes version manager with KVStore
   ↓
2. v1 package calls RegisterInitializer (via the domain's crossing wrapper) during `init(cur realm)`
   → Manager validates the realm token (rlm.IsCurrent()) and caller domain path
   → Becomes active implementation
   ↓
3. v2 package calls RegisterInitializer during `init(cur realm)`
   → Registered for later activation
   ↓
4. v3 package calls RegisterInitializer during `init(cur realm)`
   → Registered

Version Switching Flow

1. Admin/governance calls ChangeImplementation (via the domain's UpgradeImpl wrapper)
   → Authorization is enforced in the /r/ wrapper
   ↓
2. Version Manager validates the realm token (rlm.IsCurrent()), rejecting spoofed/stale tokens
   ↓
3. Version Manager retrieves v2's initializer
   ↓
4. Executes v2 initializer with shared KVStore
   ↓
5. Updates currentImplementation pointer to v2
   ↓
6. v2 is now the active implementation

Storage Access Model

  • Domain Ownership: The domain (proxy) realm owns the KVStore and has write permission
  • Explicit Realm Threading: Registration/upgrade calls thread the live crossing-frame token (rlm) into the manager instead of relying on runtime.CurrentRealm(). The manager validates it with rlm.IsCurrent() (rejecting spoofed/stale tokens) and identifies the registering version package via rlm.Previous()
  • No Direct Permission Grants: Implementation realms do not receive storage permissions directly; the proxy realm drives all storage access
  • Security by Design: External callers cannot invoke implementation realms to modify storage

Best Practices

  1. Version Registration: All versions should register during init(cur realm)
  2. Interface Compliance: Ensure all versions implement the same domain interface
  3. Storage Compatibility: Design storage schema to be forward/backward compatible
  4. Testing: Test version switching thoroughly before production use
  5. Rollback Support: Keep previous versions registered for quick rollback capability

Error Handling

The package returns errors for:

  • A spoofed or stale realm token (rlm.IsCurrent() == falseErrSpoofedRealm)
  • Unauthorized caller attempting to register (not in domain path)
  • Duplicate registration of the same package path
  • Attempting to switch to an unregistered version
  • Invalid initializer function type

Use Cases

Protocol Upgrades

Upgrade DeFi protocol logic without disrupting active users:

1// Upgrade fee calculation algorithm
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")

A/B Testing

Test new implementations before full rollout:

1// Switch to experimental version
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/experimental")
3
4// Rollback if issues detected
5protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v1")

Emergency Response

Quickly switch to a patched version during security incidents:

1// Deploy fixed version and immediately activate
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v1_hotfix")

Implementation Notes

  • Built on Strategy Pattern for runtime algorithm swapping
  • Uses Plugin Architecture for dynamic version loading
  • Storage access is driven by the proxy realm; the live realm token is threaded explicitly (the v2 _ int, rlm realm marker) and validated via rlm.IsCurrent()
  • No data migration required - all versions share the same storage
  • Type assertions required when retrieving current implementation
  • Map used for efficient initializer storage and lookup

Limitations

  • Type Safety: Requires runtime type assertion to domain interface
  • Storage Schema: Requires careful schema design for cross-version compatibility
  • Registration Order: First registered version becomes the initial active implementation
  • Domain Call Requirement: Implementation functions must be called through domain proxy for storage access
  • gno.land/p/gnoswap/store: KVStore with permission-based access control

Overview

Package version_manager provides a runtime version management system for dynamic implementation switching without data migration. It implements the Strategy Pattern combined with Plugin Architecture to enable hot-swapping between different versioned implementations of the same domain.

## Overview

Version Manager enables seamless upgrades by allowing multiple versioned implementations (v1, v2, v3) to coexist and share a unified storage layer. The domain (proxy) realm owns that storage; the manager records version initializers and swaps the active implementation reference without granting implementation realms direct write permission.

Key components of this package include:

  1. **VersionManager Interface**: Defines the contract for managing multiple versioned implementations of a domain.
  2. **versionManager Implementation**: Concrete implementation that manages version registration and active implementation switching.
  3. **Realm-Threaded Validation**: Validates live realm tokens and restricts registration to packages under the configured domain path.
  4. **Domain-Scoped Security**: Ensures only authorized packages within the domain path can register implementations.

## Key Features

  • **Zero-Downtime Upgrades**: Switch implementations at runtime without service interruption or data migration.
  • **Unified Storage**: All versions share a single KVStore owned by the domain (proxy) realm, eliminating migration overhead.
  • **Hot-Swapping**: Instant version switching through dynamic strategy replacement with explicit realm validation.
  • **Domain-Scoped Security**: Only packages under the authorized domain path can register implementations, preventing unauthorized access.
  • **Backward Compatibility**: Previous versions remain registered for gradual migration and rollback support.
  • **Strategy Pattern**: Enables runtime algorithm swapping without code changes.

## Architecture Pattern

The package implements two complementary design patterns:

  • **Strategy Pattern**: Enables runtime selection of implementation strategies
  • **Plugin Architecture**: Supports dynamic loading and registration of versions

## Workflow

Typical usage of the version_manager package includes the following steps:

  1. **Initialization**: Create a version manager for the domain using NewVersionManager.
  2. **Version Registration**: Each version (v1, v2, v3) calls RegisterInitializer during its init(cur realm) function to register its implementation.
  3. **Active Implementation**: The first registered version becomes the active implementation; subsequent versions are retained for later switching.
  4. **Version Switching**: Use ChangeImplementation to hot-swap to a different version while storage ownership stays with the domain KVStore.

## Example Usage

### Step 1: Define Domain Interface

```gno // protocol_fee/types.gno package protocol_fee

Example
1type ProtocolFee interface {
2    SetFeeRatio(ratio uint64) error
3    GetFeeRatio() uint64
4}

```

### Step 2: Create Version Manager

```gno // protocol_fee/protocol_fee.gno package protocol_fee

import (

Example
1"gno.land/p/gnoswap/version_manager"
2"gno.land/p/gnoswap/store"

)

Example
 1var manager version_manager.VersionManager
 2
 3func init(cur realm) {
 4    kvStore := store.NewKVStore(cur.Address())
 5
 6    manager = version_manager.NewVersionManager(
 7        cur.PkgPath(),
 8        kvStore,
 9        func(_ int, rlm realm, kv store.KVStore) any {
10            return NewProtocolFeeStore(kv)
11        },
12    )
13}
14
15func GetManager() version_manager.VersionManager {
16    return manager
17}

```

### Step 3: Implement Version 1

```gno // protocol_fee/v1/v1.gno package v1

import "gno.land/r/gnoswap/protocol_fee"

type protocolFeeV1 struct {

Example
1store any

}

Example
 1func init(cur realm) {
 2    // Register this version during package initialization.
 3    protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
 4        return &protocolFeeV1{store: store}
 5    })
 6}
 7
 8func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error {
 9    // v1 implementation
10    return nil
11}
12
13func (pf *protocolFeeV1) GetFeeRatio() uint64 {
14    // v1 implementation
15    return 0
16}

```

### Step 4: Implement Version 2

```gno // protocol_fee/v2/v2.gno package v2

import "gno.land/r/gnoswap/protocol_fee"

type protocolFeeV2 struct {

Example
1store any

}

Example
 1func init(cur realm) {
 2    // Register v2 - inactive until explicitly activated.
 3    protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
 4        return &protocolFeeV2{store: store}
 5    })
 6}
 7
 8func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error {
 9    // v2 improved implementation
10    return nil
11}
12
13func (pf *protocolFeeV2) GetFeeRatio() uint64 {
14    // v2 improved implementation
15    return 0
16}

```

### Step 5: Use Active Implementation

```gno // client code import "gno.land/r/gnoswap/protocol_fee"

Example
1func UseFee() {
2    manager := protocol_fee.GetManager()
3    impl := manager.GetCurrentImplementation().(protocol_fee.ProtocolFee)
4
5    ratio := impl.GetFeeRatio()
6    // Use the active version's implementation
7}

```

### Step 6: Switch Versions at Runtime

```gno // governance or admin function

Example
1func UpgradeToV2(cur realm) {
2    // Hot-swap to v2 - zero downtime.
3    protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")
4}

```

## Registration Flow

The version registration process follows this sequence:

  1. Domain package initializes version manager with KVStore ↓
  2. v1 package calls the domain registration wrapper during init(cur realm) → Becomes the active implementation ↓
  3. v2 package calls the domain registration wrapper during init(cur realm) → Registered for later activation ↓
  4. v3 package calls the domain registration wrapper during init(cur realm) → Registered for later activation

## Version Switching Flow

When switching versions, the following steps occur:

  1. Admin/governance calls the domain upgrade wrapper ↓
  2. The domain wrapper enforces authorization and calls ChangeImplementation(0, cur, ...) ↓
  3. Version Manager validates the live realm token and retrieves v2's initializer ↓
  4. Executes v2 initializer with the shared KVStore ↓
  5. Updates currentPackagePath and currentImplementation

## Storage Access Model

The version manager keeps storage ownership with the domain (proxy) realm:

  • **Domain Ownership**: The domain realm owns the KVStore and drives writes.
  • **No Direct Grants**: Implementation realms do not receive direct storage permissions from version_manager.
  • **Explicit Realm Threading**: Registration and switching calls validate the live realm token with `rlm.IsCurrent()` and identify the caller with `rlm.Previous()`.
  • **Domain Isolation**: Registration is scoped to packages under the domain path.

## Security

Domain-scoped security ensures that only authorized packages can register:

  • **Path Validation**: Caller's package path must start with the domain path + "/"
  • **Realm Verification**: Only realm (contract) code can register, not user calls
  • **Example**: For domain "gno.land/r/gnoswap/protocol_fee":
  • Valid: "gno.land/r/gnoswap/protocol_fee/v1", "gno.land/r/gnoswap/protocol_fee/v2"
  • Invalid: "gno.land/r/gnoswap/other", "gno.land/r/attacker/malicious"

## Error Handling

The package returns errors for:

  • Unauthorized caller attempting to register (not in domain path)
  • Duplicate registration of the same package path
  • Attempting to switch to an unregistered version
  • Invalid initializer function type

## Best Practices

  1. **Version Registration**: All versions should register during init(cur realm) to ensure they're available before any runtime operations.
  2. **Interface Compliance**: Ensure all versions implement the same domain interface for seamless switching.
  3. **Storage Compatibility**: Design storage schema to be forward and backward compatible across versions to prevent data corruption.
  4. **Testing**: Thoroughly test version switching in a staging environment before production use.
  5. **Rollback Support**: Keep previous versions registered to enable quick rollback if issues are detected in new versions.
  6. **Type Assertions**: Always check type assertions when retrieving the current implementation to prevent runtime panics.

## Use Cases

### Protocol Upgrades

Upgrade DeFi protocol logic without disrupting active users:

Example
1manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v2")

### A/B Testing

Test new implementations before full rollout:

Example
1// Switch to experimental version
2manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/experimental")
3
4// Rollback if issues detected
5manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v1")

### Emergency Response

Quickly switch to a patched version during security incidents:

Example
1manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v1_hotfix")

## Limitations and Considerations

  • **Type Safety**: Requires runtime type assertion to domain interface. No compile-time type checking for implementation compatibility.
  • **Atomic Switching**: Initializer side effects during ChangeImplementation are not transactionally rolled back on partial failure. Manual recovery may be required.
  • **Storage Schema**: Requires careful schema design for cross-version compatibility. Breaking schema changes require migration or careful version ordering.
  • **Registration Order**: The first registered version automatically becomes the active implementation. Plan your deployment order carefully.
  • **No Unregistration**: Once registered, a version cannot be unregistered. Plan version lifecycles accordingly.

## Related Packages

  • gno.land/p/gnoswap/store: Provides KVStore with permission-based access control

Package version_manager is intended for use in Gno smart contracts requiring dynamic, upgradeable implementations with zero-downtime version switching.

Package version_manager implements a runtime version management system using the Strategy Pattern. It enables dynamic switching between different implementation versions of the same domain (e.g., v1, v2, v3) while maintaining a unified storage layer. This approach allows for seamless upgrades without migration overhead.

Key Features:

  • Dynamic implementation registration and switching
  • Domain-scoped security (only authorized packages can register)
  • Zero-downtime upgrades through hot-swapping

Architecture Pattern: Strategy + Plugin Architecture

Constants 1

const ErrSpoofedRealm

1const ErrSpoofedRealm = "rlm does not match the current crossing frame"
source

ErrSpoofedRealm is returned when the supplied realm token does not match the live crossing frame (rlm.IsCurrent() == false). It signals a stale or spoofed token captured in an earlier frame.

Functions 1

func NewVersionManager

1func NewVersionManager(
2	domainPath string,
3	kvStore store.KVStore,
4	initializeDomainStoreFn func(_ int, rlm realm, kvStore store.KVStore) any,
5) VersionManager
source

NewVersionManager creates a new version manager instance for a specific domain. This should be called once per domain during system initialization.

Parameters:

  • domainPath: The base package path for the domain (e.g., "gno.land/r/gnoswap/protocol_fee") Used for access control to ensure only authorized packages can register

  • kvStore: The shared key-value store that all versions will access The domain realm (proxy) is the owner and has write permission to this store

  • initializeDomainStoreFn: A factory function that wraps the KVStore into a domain-specific storage interface This abstraction allows each version to work with a familiar storage API Example: func(_ int, rlm realm, kvStore store.KVStore) any { return NewProtocolFeeStore(kvStore) }

Returns:

  • VersionManager: An initialized version manager ready to accept implementation registrations

Usage Pattern:

  1. Create version manager in parent domain package
  2. Each version (v1, v2, v3) calls RegisterInitializer during their init()
  3. Use ChangeImplementation to switch between versions at runtime

Types 1

type VersionManager

interface
 1type VersionManager interface {
 2	// RegisterInitializer registers a version's implementation.
 3	// Must be called by each version package during initialization.
 4	// First registration becomes the active implementation.
 5	// Subsequent registrations are retained for later switching.
 6	//
 7	// The leading `_ int, rlm realm` is the v2 interrealm-spec marker pattern:
 8	// the `0` sentinel surfaces at every call site so the realm threading is
 9	// visible to the reader, and rlm is the live crossing-frame token that
10	// the implementation validates via rlm.IsCurrent() and reads via
11	// rlm.Previous() to identify the version package.
12	RegisterInitializer(_ int, rlm realm, initializer func(_ int, rlm realm, store any) any) error
13
14	// ChangeImplementation switches the active version at runtime.
15	// This enables hot-swapping without downtime or data migration while storage
16	// ownership stays with the domain KVStore.
17	//
18	// The leading `_ int, rlm realm` is the v2 interrealm-spec marker pattern;
19	// rlm is validated via rlm.IsCurrent() to reject spoofed/stale tokens.
20	// Upgrade ACLs live in the wrapping /r/ realm, not here.
21	ChangeImplementation(_ int, rlm realm, packagePath string) error
22
23	// GetDomainPath returns the base domain path (e.g., "gno.land/r/gnoswap/protocol_fee")
24	GetDomainPath() string
25
26	// GetInitializers returns all registered version initializers
27	GetInitializers() map[string]func(_ int, rlm realm, store any) any
28
29	// GetCurrentPackagePath returns the package path of the active implementation
30	GetCurrentPackagePath() string
31
32	// GetCurrentImplementation returns the active version instance
33	// The caller should type-assert this to the domain-specific interface
34	GetCurrentImplementation() any
35}
source

VersionManager defines the interface for managing multiple versioned implementations of a domain. It enables the Strategy Pattern at the package level, allowing runtime switching between different versions (v1, v2, v3, etc.) without requiring data migration.

Design Goals:

  • Enable zero-downtime upgrades through hot-swapping implementations
  • Maintain a single source of truth for storage across all versions
  • Enforce security through domain-scoped registration
  • Support backward compatibility by keeping old versions registered for later activation

Implementation Note: The actual implementations of each version must satisfy a common domain interface defined by the specific domain (e.g., ProtocolFee interface for protocol_fee domain).

Imports 4

Source Files 5