/p/gnoswap/version_manager/v1
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 onruntime.CurrentRealm(). The manager validates it withrlm.IsCurrent()(rejecting spoofed/stale tokens) and identifies the registering version package viarlm.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
- Version Registration: All versions should register during
init(cur realm) - Interface Compliance: Ensure all versions implement the same domain interface
- Storage Compatibility: Design storage schema to be forward/backward compatible
- Testing: Test version switching thoroughly before production use
- 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() == false→ErrSpoofedRealm) - 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 realmmarker) and validated viarlm.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
Related Packages
gno.land/p/gnoswap/store/v1: KVStore with permission-based access control