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

doc.gno

11.14 Kb · 323 lines
  1// Package version_manager provides a runtime version management system for dynamic
  2// implementation switching without data migration. It implements the Strategy Pattern
  3// combined with Plugin Architecture to enable hot-swapping between different versioned
  4// implementations of the same domain.
  5//
  6// ## Overview
  7//
  8// Version Manager enables seamless upgrades by allowing multiple versioned implementations
  9// (v1, v2, v3) to coexist and share a unified storage layer. The domain
 10// (proxy) realm owns that storage; the manager records version initializers and
 11// swaps the active implementation reference without granting implementation
 12// realms direct write permission.
 13//
 14// Key components of this package include:
 15//
 16//  1. **VersionManager Interface**: Defines the contract for managing multiple versioned
 17//     implementations of a domain.
 18//  2. **versionManager Implementation**: Concrete implementation that manages version
 19//     registration and active implementation switching.
 20//  3. **Realm-Threaded Validation**: Validates live realm tokens and restricts
 21//     registration to packages under the configured domain path.
 22//  4. **Domain-Scoped Security**: Ensures only authorized packages within the domain
 23//     path can register implementations.
 24//
 25// ## Key Features
 26//
 27//   - **Zero-Downtime Upgrades**: Switch implementations at runtime without service
 28//     interruption or data migration.
 29//   - **Unified Storage**: All versions share a single KVStore owned by the
 30//     domain (proxy) realm, eliminating migration overhead.
 31//   - **Hot-Swapping**: Instant version switching through dynamic strategy replacement
 32//     with explicit realm validation.
 33//   - **Domain-Scoped Security**: Only packages under the authorized domain path can
 34//     register implementations, preventing unauthorized access.
 35//   - **Backward Compatibility**: Previous versions remain registered for
 36//     gradual migration and rollback support.
 37//   - **Strategy Pattern**: Enables runtime algorithm swapping without code changes.
 38//
 39// ## Architecture Pattern
 40//
 41// The package implements two complementary design patterns:
 42//
 43//   - **Strategy Pattern**: Enables runtime selection of implementation strategies
 44//   - **Plugin Architecture**: Supports explicit registration of version packages
 45//
 46// ## Workflow
 47//
 48// Typical usage of the version_manager package includes the following steps:
 49//
 50//  1. **Initialization**: Create a version manager for the domain using NewVersionManager.
 51//  2. **Version Registration**: Each version (v1, v2, v3) calls RegisterInitializer during
 52//     its init(cur realm) function to register its implementation.
 53//  3. **Active Implementation**: The first registered version becomes the active implementation;
 54//     subsequent versions are retained for later switching.
 55//  4. **Version Switching**: Use ChangeImplementation to hot-swap to a different version
 56//     while storage ownership stays with the domain KVStore.
 57//
 58// ## Example Usage
 59//
 60// ### Step 1: Define Domain Interface
 61//
 62// ```gno
 63// // protocol_fee/types.gno
 64// package protocol_fee
 65//
 66//	type ProtocolFee interface {
 67//	    SetFeeRatio(ratio uint64) error
 68//	    GetFeeRatio() uint64
 69//	}
 70//
 71// ```
 72//
 73// ### Step 2: Create Version Manager
 74//
 75// ```gno
 76// // protocol_fee/protocol_fee.gno
 77// package protocol_fee
 78//
 79// import (
 80//
 81//	"gno.land/p/gnoswap/version_manager/v1"
 82//	"gno.land/p/gnoswap/store/v1"
 83//
 84// )
 85//
 86//	var manager version_manager.VersionManager
 87//
 88//	func init(cur realm) {
 89//	    kvStore := store.NewKVStore(cur.Address())
 90//
 91//	    manager = version_manager.NewVersionManager(
 92//	        cur.PkgPath(),
 93//	        kvStore,
 94//	        func(_ int, rlm realm, kv store.KVStore) any {
 95//	            return NewProtocolFeeStore(kv)
 96//	        },
 97//	    )
 98//	}
 99//
100//	func GetManager() version_manager.VersionManager {
101//	    return manager
102//	}
103//
104// ```
105//
106// ### Step 3: Implement Version 1
107//
108// ```gno
109// // protocol_fee/v1/v1.gno
110// package v1
111//
112// import "gno.land/r/gnoswap/protocol_fee"
113//
114// type protocolFeeV1 struct {
115//
116//	store any
117//
118// }
119//
120//	func init(cur realm) {
121//	    // Register this version during package initialization.
122//	    protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
123//	        return &protocolFeeV1{store: store}
124//	    })
125//	}
126//
127//	func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error {
128//	    // v1 implementation
129//	    return nil
130//	}
131//
132//	func (pf *protocolFeeV1) GetFeeRatio() uint64 {
133//	    // v1 implementation
134//	    return 0
135//	}
136//
137// ```
138//
139// ### Step 4: Implement Version 2
140//
141// ```gno
142// // protocol_fee/v2/v2.gno
143// package v2
144//
145// import "gno.land/r/gnoswap/protocol_fee"
146//
147// type protocolFeeV2 struct {
148//
149//	store any
150//
151// }
152//
153//	func init(cur realm) {
154//	    // Register v2 - inactive until explicitly activated.
155//	    protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any {
156//	        return &protocolFeeV2{store: store}
157//	    })
158//	}
159//
160//	func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error {
161//	    // v2 improved implementation
162//	    return nil
163//	}
164//
165//	func (pf *protocolFeeV2) GetFeeRatio() uint64 {
166//	    // v2 improved implementation
167//	    return 0
168//	}
169//
170// ```
171//
172// ### Step 5: Use Active Implementation
173//
174// ```gno
175// // client code
176// import "gno.land/r/gnoswap/protocol_fee"
177//
178//	func UseFee() {
179//	    manager := protocol_fee.GetManager()
180//	    impl := manager.GetCurrentImplementation().(protocol_fee.ProtocolFee)
181//
182//	    ratio := impl.GetFeeRatio()
183//	    // Use the active version's implementation
184//	}
185//
186// ```
187//
188// ### Step 6: Switch Versions at Runtime
189//
190// ```gno
191// // governance or admin function
192//
193//	func UpgradeToV2(cur realm) {
194//	    // Hot-swap to v2 - zero downtime.
195//	    protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2")
196//	}
197//
198// ```
199//
200// ## Registration Flow
201//
202// The version registration process follows this sequence:
203//
204//  1. Domain package initializes version manager with KVStore
205//     ↓
206//  2. v1 package calls the domain registration wrapper during init(cur realm)
207//     → Becomes the active implementation
208//     ↓
209//  3. v2 package calls the domain registration wrapper during init(cur realm)
210//     → Registered for later activation
211//     ↓
212//  4. v3 package calls the domain registration wrapper during init(cur realm)
213//     → Registered for later activation
214//
215// ## Version Switching Flow
216//
217// When switching versions, the following steps occur:
218//
219//  1. Admin/governance calls the domain upgrade wrapper
220//     ↓
221//  2. The domain wrapper enforces authorization and calls ChangeImplementation(0, cur, ...)
222//     ↓
223//  3. Version Manager validates the live realm token and retrieves v2's initializer
224//     ↓
225//  4. Executes v2 initializer with the shared KVStore
226//     ↓
227//  5. Updates currentPackagePath and currentImplementation
228//
229// ## Storage Access Model
230//
231// The version manager keeps storage ownership with the domain (proxy) realm:
232//
233//   - **Domain Ownership**: The domain realm owns the KVStore and drives writes.
234//   - **No Direct Grants**: Implementation realms do not receive direct storage
235//     permissions from version_manager.
236//   - **Explicit Realm Threading**: Registration and switching calls validate the
237//     live realm token with `rlm.IsCurrent()` and identify the caller with
238//     `rlm.Previous()`.
239//   - **Domain Isolation**: Registration is scoped to packages under the domain path.
240//
241// ## Security
242//
243// Domain-scoped security ensures that only authorized packages can register:
244//
245//   - **Path Validation**: Caller's package path must start with the domain path + "/"
246//   - **Realm Verification**: Only realm (contract) code can register, not user calls
247//   - **Example**: For domain "gno.land/r/gnoswap/protocol_fee":
248//   - Valid: "gno.land/r/gnoswap/protocol_fee/v1", "gno.land/r/gnoswap/protocol_fee/v2"
249//   - Invalid: "gno.land/r/gnoswap/other", "gno.land/r/attacker/malicious"
250//
251// ## Error Handling
252//
253// The package returns errors for:
254//
255//   - Unauthorized caller attempting to register (not in domain path)
256//   - Duplicate registration of the same package path
257//   - Attempting to switch to an unregistered version
258//   - A nil initializer in the registered map (an internal invalid state).
259//     The initializer function signature is checked at compile time by the typed API.
260//
261// ## Best Practices
262//
263//  1. **Version Registration**: All versions should register during init(cur realm) to ensure
264//     they're available before any runtime operations.
265//  2. **Interface Compliance**: Ensure all versions implement the same domain interface
266//     for seamless switching.
267//  3. **Storage Compatibility**: Design storage schema to be forward and backward
268//     compatible across versions to prevent data corruption.
269//  4. **Testing**: Thoroughly test version switching in a staging environment before
270//     production use.
271//  5. **Rollback Support**: Keep previous versions registered to enable quick rollback
272//     if issues are detected in new versions.
273//  6. **Type Assertions**: Always check type assertions when retrieving the current
274//     implementation to prevent runtime panics.
275//
276// ## Use Cases
277//
278// ### Protocol Upgrades
279//
280// Upgrade DeFi protocol logic without disrupting active users. The target
281// version package must already be deployed/loaded and must have registered its
282// initializer during package initialization:
283//
284//	manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v2")
285//
286// ### A/B Testing
287//
288// Test new implementations before full rollout. Switch only to paths whose
289// version packages have already registered initializers:
290//
291//	// Switch to a registered experimental version
292//	manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/experimental")
293//
294//	// Rollback to another registered version
295//	manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v1")
296//
297// ### Emergency Response
298//
299// Quickly switch to a patched version during security incidents. Deploy/load
300// the hotfix package and register its initializer before activation:
301//
302//	manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/v1_hotfix")
303//
304// ## Limitations and Considerations
305//
306//   - **Type Safety**: Requires runtime type assertion to domain interface. No compile-time
307//     type checking for implementation compatibility.
308//   - **Atomic Switching**: Initializer side effects during ChangeImplementation are not
309//     transactionally rolled back on partial failure. Manual recovery may be required.
310//   - **Storage Schema**: Requires careful schema design for cross-version compatibility.
311//     Breaking schema changes require migration or careful version ordering.
312//   - **Registration Order**: The first registered version automatically becomes the
313//     active implementation. Plan your deployment order carefully.
314//   - **No Unregistration**: Once registered, a version cannot be unregistered. Plan
315//     version lifecycles accordingly.
316//
317// ## Related Packages
318//
319//   - gno.land/p/gnoswap/store/v1: Provides KVStore with permission-based access control
320//
321// Package version_manager is intended for use in Gno smart contracts requiring
322// dynamic, upgradeable implementations with zero-downtime version switching.
323package version_manager