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

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

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
  • A nil initializer in the registered map (ChangeImplementation's internal invalid-state check). The initializer function signature is checked at compile time by the typed API.

Use Cases

Protocol Upgrades

Upgrade DeFi protocol logic without disrupting active users. The target version must already be deployed/loaded and must have registered its initializer during package initialization; UpgradeImpl only activates registered paths:

1// The protocol_fee/v2 package has already registered this path during init.
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")

A/B Testing

Test a new implementation before full rollout. Deploy/load the package and let its init call RegisterInitializer before switching:

1// v2 was deployed and registered before this call.
2protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")
3
4// Roll back to another path that was also registered during initialization.
5protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v1")

Emergency Response

Quickly switch to a patched version during security incidents. The hotfix package must be deployed/loaded and registered before activation:

1// v1_hotfix was deployed and registered during its package init.
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 explicit version registration
  • 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
  • Initializers are registered by version packages; the manager does not load or deploy packages itself

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/v1: 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 explicit registration of version packages

## 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/v1"
2"gno.land/p/gnoswap/store/v1"

)

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
  • A nil initializer in the registered map (an internal invalid state). The initializer function signature is checked at compile time by the typed API.

## 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. The target version package must already be deployed/loaded and must have registered its initializer during package initialization:

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

### A/B Testing

Test new implementations before full rollout. Switch only to paths whose version packages have already registered initializers:

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

### Emergency Response

Quickly switch to a patched version during security incidents. Deploy/load the hotfix package and register its initializer before activation:

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/v1: 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	// Parameters:
 8	//   - _: Interrealm-call discriminator; callers pass 0.
 9	//   - rlm: Propagated current realm context from the domain wrapper; implementations validate the current frame and inspect its previous frame to identify the registering version package.
10	//   - initializer: Callback receiving the discriminator, realm context, and domain storage wrapper, and returning that version's implementation instance.
11	//
12	// Returns:
13	//   - error: nil when the version is registered; otherwise an error for a spoofed or unauthorized caller, duplicate registration, or nil initializer.
14	RegisterInitializer(_ int, rlm realm, initializer func(_ int, rlm realm, store any) any) error
15
16	// ChangeImplementation switches the active version at runtime.
17	// The domain retains its KVStore; the selected initializer must handle state
18	// compatibility. The manager does not provide automatic schema migration.
19	//
20	// Parameters:
21	//   - _: Interrealm-call discriminator; callers pass 0.
22	//   - rlm: Propagated current realm context from the domain wrapper; implementations validate the current frame before switching.
23	//   - packagePath: Full package path of a version previously registered with RegisterInitializer.
24	//
25	// Returns:
26	//   - error: nil when the registered version becomes active; otherwise an error for a spoofed realm or unknown or invalid initializer.
27	ChangeImplementation(_ int, rlm realm, packagePath string) error
28
29	// GetDomainPath returns the base domain path (e.g., "gno.land/r/gnoswap/protocol_fee").
30	//
31	// Returns:
32	//   - string: Base package path used to scope this version manager's implementations.
33	GetDomainPath() string
34
35	// GetInitializers returns all registered version initializers.
36	//
37	// Returns:
38	//   - map[string]func(_ int, rlm realm, store any) any: Registry mapping each version package path to its initializer callback.
39	GetInitializers() map[string]func(_ int, rlm realm, store any) any
40
41	// GetCurrentPackagePath returns the package path of the active implementation.
42	//
43	// Returns:
44	//   - string: Package path of the active implementation, or the empty string before any version is registered.
45	GetCurrentPackagePath() string
46
47	// GetCurrentImplementation returns the active version instance.
48	// The caller should type-assert this to the domain-specific interface.
49	//
50	// Returns:
51	//   - any: Active version implementation instance, or nil before the first registration.
52	GetCurrentImplementation() any
53}
source

VersionManager defines the interface for managing multiple versioned implementations of a domain. It switches implementations while retaining domain-owned storage. Each version is responsible for compatibility with that state and any required migration.

Design Goals:

  • Switch registered implementations without redeploying the domain
  • Maintain a single source of truth for storage across all versions
  • Enforce security through domain-scoped registration
  • Retain registered versions for later activation, subject to state compatibility

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