const ErrSpoofedRealm
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.
Package version\_manager provides a runtime version management system for dynamic implementation switching without da...
Runtime version management system for dynamic implementation switching without data migration.
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.
Pattern: Strategy + Plugin Architecture
1// protocol_fee/types.gno
2package protocol_fee
3
4type ProtocolFee interface {
5 SetFeeRatio(ratio uint64) error
6 GetFeeRatio() uint64
7}
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}
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}
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}
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}
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
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
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()init(cur realm)The package returns errors for:
rlm.IsCurrent() == false → ErrSpoofedRealm)ChangeImplementation's internal
invalid-state check). The initializer function signature is checked at
compile time by the typed API.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")
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")
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")
_ int, rlm realm marker) and validated via rlm.IsCurrent()gno.land/p/gnoswap/store/v1: KVStore with permission-based access controlPackage 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:
## Key Features
## Architecture Pattern
The package implements two complementary design patterns:
## Workflow
Typical usage of the version_manager package includes the following steps:
## Example Usage
### Step 1: Define Domain Interface
```gno // protocol_fee/types.gno package protocol_fee
```
### Step 2: Create Version Manager
```gno // protocol_fee/protocol_fee.gno package protocol_fee
import (
)
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 {
1store any
}
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 {
1store any
}
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"
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
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:
## Version Switching Flow
When switching versions, the following steps occur:
## Storage Access Model
The version manager keeps storage ownership with the domain (proxy) realm:
## Security
Domain-scoped security ensures that only authorized packages can register:
## Error Handling
The package returns errors for:
## Best Practices
## 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:
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:
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:
1manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v1_hotfix")
## Limitations and Considerations
## Related Packages
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:
Architecture Pattern: Strategy + Plugin Architecture
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.
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:
Usage Pattern:
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}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:
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).