/p/gnoswap/version_manager
Directory · 5 Files
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() {
10 kvStore := store.NewKVStore("protocol_fee")
11
12 manager = version_manager.NewVersionManager(
13 "gno.land/r/gnoswap/protocol_fee",
14 kvStore,
15 func(kv store.KVStore) any {
16 return NewProtocolFeeStore(kv)
17 },
18 )
19}
20
21func GetManager() version_manager.VersionManager {
22 return manager
23}
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() {
11 // Register this version during package initialization
12 manager := protocol_fee.GetManager()
13 manager.RegisterInitializer(func(store any) any {
14 return &protocolFeeV1{store: store}
15 })
16}
17
18func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error {
19 // v1 implementation
20}
21
22func (pf *protocolFeeV1) GetFeeRatio() uint64 {
23 // v1 implementation
24}
1// protocol_fee/v2/v2.gno
2package v2
3
4type protocolFeeV2 struct {
5 store any
6}
7
8func init() {
9 // Register v2
10 manager := protocol_fee.GetManager()
11 manager.RegisterInitializer(func(store any) any {
12 return &protocolFeeV2{store: store}
13 })
14}
15
16func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error {
17 // v2 improved implementation
18}
19
20func (pf *protocolFeeV2) GetFeeRatio() uint64 {
21 // v2 improved implementation
22}
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 function
2func UpgradeToV2() error {
3 manager := protocol_fee.GetManager()
4
5 // Hot-swap to v2 - zero downtime
6 return manager.ChangeImplementation("gno.land/r/gnoswap/protocol_fee/v2")
7}
Workflow
Registration Flow
1. Domain package initializes version manager with KVStore
↓
2. v1 package calls RegisterInitializer during init()
→ Becomes active implementation
↓
3. v2 package calls RegisterInitializer during init()
→ Registered
↓
4. v3 package calls RegisterInitializer during init()
→ Registered
Version Switching Flow
1. Admin calls ChangeImplementation("path/to/v2")
↓
2. Version Manager retrieves v2's initializer
↓
3. Executes v2 initializer with shared KVStore
↓
4. Updates currentImplementation pointer to v2
↓
5. v2 is now the active implementation
Storage Access Model
- Domain Ownership: The domain (proxy) realm owns the KVStore and has write permission
- Realm Context Preservation: When domain calls implementation,
runtime.CurrentRealm()remains the domain realm - No Direct Permission Grants: Implementation realms do not receive storage permissions directly
- Security by Design: External callers cannot invoke implementation realms to modify storage
Best Practices
- Version Registration: All versions should register during
init() - 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:
- 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
2manager.ChangeImplementation("gno.land/r/gnoswap/protocol_fee/v2")
A/B Testing
Test new implementations before full rollout:
1// Switch to experimental version
2manager.ChangeImplementation("gno.land/r/gnoswap/protocol_fee/experimental")
3
4// Rollback if issues detected
5manager.ChangeImplementation("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
2manager.ChangeImplementation("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 controlled via realm context (
runtime.CurrentRealm()) - No data migration required - all versions share the same storage
- Type assertions required when retrieving current implementation
- AVL tree 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
Related Packages
gno.land/p/gnoswap/store: KVStore with permission-based access controlgno.land/p/nt/avl: AVL tree for initializer storage