Transaction

C873C718F27D22…8D1F0C1BAA85

Block 77,243 · index 0 · indexed

Summary

Hash
C873C718F27D221B5DA7BDF99883CC53D44D03A7E863B66D84F58D1F0C1BAA85
Block
77,243
Size
36266 bytes
Gas used
39,659,381 / 47,591,222
Fee
47592ugnot
Status
success

Messages

Arguments · 13

  1. #1version_manager
  2. #2README.md
  3. #3# 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 ```go // protocol_fee/types.gno package protocol_fee type ProtocolFee interface { SetFeeRatio(ratio uint64) error GetFeeRatio() uint64 } ``` ### Step 2: Create Version Manager ```go // protocol_fee/protocol_fee.gno package protocol_fee import ( "gno.land/p/gnoswap/store/v1" "gno.land/p/gnoswap/version_manager/v1" ) var manager version_manager.VersionManager func init(cur realm) { kvStore := store.NewKVStore(cur.Address()) manager = version_manager.NewVersionManager( cur.PkgPath(), kvStore, // initializeDomainStoreFn carries the v2 interrealm marker (`_ int, rlm realm`): // the leading 0 surfaces realm-threading at the call site. func(_ int, rlm realm, kv store.KVStore) any { return NewProtocolFeeStore(kv) }, ) } func GetManager() version_manager.VersionManager { return manager } // RegisterInitializer is the crossing entry point each version package calls. // `cur` is the live crossing-frame realm token; it is threaded straight into the // version manager (the leading 0 is the v2 sentinel) so the manager can reject // spoofed/stale tokens via rlm.IsCurrent() and identify the caller via rlm.Previous(). func RegisterInitializer(cur realm, initializer func(_ int, rlm realm, store any) any) { if err := manager.RegisterInitializer(0, cur, initializer); err != nil { panic(err) } } // UpgradeImpl switches the active version. Authorization (admin / governance) is // enforced here in the /r/ realm; version_manager only rejects spoofed tokens. func UpgradeImpl(cur realm, packagePath string) { if err := manager.ChangeImplementation(0, cur, packagePath); err != nil { panic(err) } } ``` ### Step 3: Implement Versions ```go // protocol_fee/v1/v1.gno package v1 import "gno.land/r/gnoswap/protocol_fee" type protocolFeeV1 struct { store any } func init(cur realm) { // Register this version during package initialization. // `cross(cur)` invokes the domain's crossing entry point, which threads the // live realm token into the version manager. protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any { return &protocolFeeV1{store: store} }) } func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error { // v1 implementation } func (pf *protocolFeeV1) GetFeeRatio() uint64 { // v1 implementation } ``` ```go // protocol_fee/v2/v2.gno package v2 type protocolFeeV2 struct { store any } func init(cur realm) { // Register v2 — inactive until explicitly activated. protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any { return &protocolFeeV2{store: store} }) } func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error { // v2 improved implementation } func (pf *protocolFeeV2) GetFeeRatio() uint64 { // v2 improved implementation } ``` ### Step 4: Use Active Implementation ```go // client code import "gno.land/r/gnoswap/protocol_fee" func UseFee() { manager := protocol_fee.GetManager() impl := manager.GetCurrentImplementation().(protocol_fee.ProtocolFee) ratio := impl.GetFeeRatio() // Use the active version's implementation } ``` ### Step 5: Switch Versions at Runtime ```go // governance or admin entry point func UpgradeToV2(cur realm) { // Hot-swap to v2 — zero downtime. `cross(cur)` enters UpgradeImpl's crossing // frame; UpgradeImpl threads the realm token into the version manager. protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2") } ``` ## 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() == 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: ```go // The protocol_fee/v2 package has already registered this path during init. protocol_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: ```go // v2 was deployed and registered before this call. protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2") // Roll back to another path that was also registered during initialization. protocol_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: ```go // v1_hotfix was deployed and registered during its package init. protocol_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 ## Related Packages - `gno.land/p/gnoswap/store/v1`: KVStore with permission-based access control
  4. #4doc.gno
  5. #5// 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 // // type ProtocolFee interface { // SetFeeRatio(ratio uint64) error // GetFeeRatio() uint64 // } // // ``` // // ### Step 2: Create Version Manager // // ```gno // // protocol_fee/protocol_fee.gno // package protocol_fee // // import ( // // "gno.land/p/gnoswap/version_manager/v1" // "gno.land/p/gnoswap/store/v1" // // ) // // var manager version_manager.VersionManager // // func init(cur realm) { // kvStore := store.NewKVStore(cur.Address()) // // manager = version_manager.NewVersionManager( // cur.PkgPath(), // kvStore, // func(_ int, rlm realm, kv store.KVStore) any { // return NewProtocolFeeStore(kv) // }, // ) // } // // func GetManager() version_manager.VersionManager { // return manager // } // // ``` // // ### Step 3: Implement Version 1 // // ```gno // // protocol_fee/v1/v1.gno // package v1 // // import "gno.land/r/gnoswap/protocol_fee" // // type protocolFeeV1 struct { // // store any // // } // // func init(cur realm) { // // Register this version during package initialization. // protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any { // return &protocolFeeV1{store: store} // }) // } // // func (pf *protocolFeeV1) SetFeeRatio(ratio uint64) error { // // v1 implementation // return nil // } // // func (pf *protocolFeeV1) GetFeeRatio() uint64 { // // v1 implementation // return 0 // } // // ``` // // ### Step 4: Implement Version 2 // // ```gno // // protocol_fee/v2/v2.gno // package v2 // // import "gno.land/r/gnoswap/protocol_fee" // // type protocolFeeV2 struct { // // store any // // } // // func init(cur realm) { // // Register v2 - inactive until explicitly activated. // protocol_fee.RegisterInitializer(cross(cur), func(_ int, rlm realm, store any) any { // return &protocolFeeV2{store: store} // }) // } // // func (pf *protocolFeeV2) SetFeeRatio(ratio uint64) error { // // v2 improved implementation // return nil // } // // func (pf *protocolFeeV2) GetFeeRatio() uint64 { // // v2 improved implementation // return 0 // } // // ``` // // ### Step 5: Use Active Implementation // // ```gno // // client code // import "gno.land/r/gnoswap/protocol_fee" // // func UseFee() { // manager := protocol_fee.GetManager() // impl := manager.GetCurrentImplementation().(protocol_fee.ProtocolFee) // // ratio := impl.GetFeeRatio() // // Use the active version's implementation // } // // ``` // // ### Step 6: Switch Versions at Runtime // // ```gno // // governance or admin function // // func UpgradeToV2(cur realm) { // // Hot-swap to v2 - zero downtime. // protocol_fee.UpgradeImpl(cross(cur), "gno.land/r/gnoswap/protocol_fee/v2") // } // // ``` // // ## 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: // // manager.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: // // // Switch to a registered experimental version // manager.ChangeImplementation(0, cur, "gno.land/r/gnoswap/protocol_fee/experimental") // // // Rollback to another registered version // manager.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: // // manager.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
  6. #6gnomod.toml
  7. #7module = "gno.land/p/gnoswap/version_manager/v1" gno = "0.9"
  8. #8types.gno
  9. #9package version_manager // 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). type VersionManager interface { // RegisterInitializer registers a version's implementation. // Must be called by each version package during initialization. // First registration becomes the active implementation. // Subsequent registrations are retained for later switching. // // Parameters: // - _: Interrealm-call discriminator; callers pass 0. // - 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. // - initializer: Callback receiving the discriminator, realm context, and domain storage wrapper, and returning that version's implementation instance. // // Returns: // - error: nil when the version is registered; otherwise an error for a spoofed or unauthorized caller, duplicate registration, or nil initializer. RegisterInitializer(_ int, rlm realm, initializer func(_ int, rlm realm, store any) any) error // ChangeImplementation switches the active version at runtime. // The domain retains its KVStore; the selected initializer must handle state // compatibility. The manager does not provide automatic schema migration. // // Parameters: // - _: Interrealm-call discriminator; callers pass 0. // - rlm: Propagated current realm context from the domain wrapper; implementations validate the current frame before switching. // - packagePath: Full package path of a version previously registered with RegisterInitializer. // // Returns: // - error: nil when the registered version becomes active; otherwise an error for a spoofed realm or unknown or invalid initializer. ChangeImplementation(_ int, rlm realm, packagePath string) error // GetDomainPath returns the base domain path (e.g., "gno.land/r/gnoswap/protocol_fee"). // // Returns: // - string: Base package path used to scope this version manager's implementations. GetDomainPath() string // GetInitializers returns all registered version initializers. // // Returns: // - map[string]func(_ int, rlm realm, store any) any: Registry mapping each version package path to its initializer callback. GetInitializers() map[string]func(_ int, rlm realm, store any) any // GetCurrentPackagePath returns the package path of the active implementation. // // Returns: // - string: Package path of the active implementation, or the empty string before any version is registered. GetCurrentPackagePath() string // GetCurrentImplementation returns the active version instance. // The caller should type-assert this to the domain-specific interface. // // Returns: // - any: Active version implementation instance, or nil before the first registration. GetCurrentImplementation() any }
  10. #10version_manager.gno
  11. #11// 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 package version_manager import ( "chain" "errors" "strings" "gno.land/p/gnoswap/store/v1" ) // 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. const ErrSpoofedRealm = "rlm does not match the current crossing frame" // versionManager is the concrete implementation of VersionManager interface. // It manages multiple versioned implementations of a domain (e.g., protocol_fee/v1, protocol_fee/v2). // // Storage Access Model: // Implementation realms do NOT receive direct storage permissions. Instead, when calls flow // from the domain proxy to the implementation, the proxy realm (which has write permission // to the KVStore) is the one that drives the storage. This design prevents external callers // from directly invoking implementation realms to modify storage. type versionManager struct { // initializers stores registered initializer functions keyed by package path // Each initializer bootstraps a specific version's implementation initializers map[string]func(_ int, rlm realm, store any) any // domainKVStore is the shared storage layer accessible by all versions // The domain (proxy) realm is the owner and has write permission domainKVStore store.KVStore // initializeDomainStoreFn wraps the KVStore into domain-specific storage interface // This abstraction decouples the version manager from domain-specific storage implementations initializeDomainStoreFn func(_ int, rlm realm, kvStore store.KVStore) any // domainPath defines the base path for this domain (e.g., "gno.land/r/gnoswap/protocol_fee") // Used for security validation to ensure only authorized packages can register domainPath string // currentPackagePath holds the package path of the active implementation // (e.g., "gno.land/r/gnoswap/protocol_fee/v2") currentPackagePath string // currentImplementation is the active version's instance currentImplementation any } // RegisterInitializer registers a new version implementation for the domain. // This method must be called by each version package (e.g., v1, v2) during initialization. // // The registration process: // 1. Validates the realm token is the live crossing frame (rejects spoofed tokens) // 2. Validates the caller is within the authorized domain path // 3. Stores the initializer function for later version switching // // Parameters: // - _: Interrealm-call discriminator; callers pass 0. // - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() and inspects rlm.Previous() to identify and authorize the registering version package. // - initializer: Callback receiving the discriminator, current realm context, and domain-specific storage wrapper, and returning the version implementation instance. // // Returns: // - error: nil when the initializer is registered (and the first one is activated); otherwise an error for a spoofed realm, nil initializer, user caller, caller outside the domain path, or duplicate package path. // // Security: Only packages under the domainPath prefix can register (enforced by isContainDomainPath). func (vm *versionManager) RegisterInitializer(_ int, rlm realm, initializer func(_ int, rlm realm, store any) any) error { if !rlm.IsCurrent() { return errors.New(ErrSpoofedRealm) } // Validate initializer is not nil to prevent panic during initialization if initializer == nil { return errors.New("version_manager: initializer cannot be nil") } // Ensure the caller is within the domain path (e.g., protocol_fee/v1, protocol_fee/v2). // rlm.Previous() corresponds to v1's runtime.PreviousRealm(). previousRealm := rlm.Previous() if previousRealm.IsUser() { return errors.New("version_manager: caller cannot be user") } targetPackagePath := previousRealm.PkgPath() if !vm.isContainDomainPath(targetPackagePath) { return errors.New("version_manager: caller is not in the domain path") } // Check if this package path has already been registered if _, ok := vm.initializers[targetPackagePath]; ok { return errors.New("version_manager: initializer already registered") } // Register the initializer function for this package path vm.initializers[targetPackagePath] = initializer chain.Emit( "RegisterInitializer", "domainPath", vm.domainPath, "registeredPackagePath", targetPackagePath, ) // Initialize the current implementation if it hasn't been done yet if vm.currentPackagePath == "" || vm.currentImplementation == nil { vm.currentPackagePath = targetPackagePath vm.currentImplementation = initializer(0, rlm, vm.initializeDomainStoreFn(0, rlm, vm.domainKVStore)) chain.Emit( "InitializeImplementation", "domainPath", vm.domainPath, "newPackagePath", targetPackagePath, ) } return nil } // ChangeImplementation performs a hot-swap to a different version implementation. // This enables zero-downtime upgrades by switching the active implementation at runtime. // // The switching process: // 1. Validates the realm token is the live crossing frame (rejects spoofed tokens) // 2. Validates the target version has been registered via RegisterInitializer // 3. Retrieves and executes the target version's initializer // // Authorization is the caller realm's responsibility — version_manager only rejects // spoofed realm tokens. Upgrade ACLs (admin / governance) live in the wrapping /r/ realm // (see each module's upgrade.gno). // // Parameters: // - _: Interrealm-call discriminator; callers pass 0. // - rlm: Propagated current realm context from the domain wrapper; this implementation validates rlm.IsCurrent() before switching. // - packagePath: Full package path of the target version; it must be a key in the registered initializer map. // // Returns: // - error: nil when packagePath becomes active; otherwise ErrSpoofedRealm, an unknown-package error, or an invalid-initializer error. func (vm *versionManager) ChangeImplementation(_ int, rlm realm, packagePath string) error { if !rlm.IsCurrent() { return errors.New(ErrSpoofedRealm) } // Retrieve the registered initializer function initializer, ok := vm.initializers[packagePath] if !ok { return errors.New("version_manager: initializer not found for package path:" + packagePath) } if initializer == nil { return errors.New("version_manager: initializer is not a function") } prevPackagePath := vm.currentPackagePath vm.currentPackagePath = packagePath vm.currentImplementation = initializer(0, rlm, vm.initializeDomainStoreFn(0, rlm, vm.domainKVStore)) chain.Emit( "ChangeImplementation", "domainPath", vm.domainPath, "previousPackagePath", prevPackagePath, "newPackagePath", packagePath, ) return nil } // GetDomainPath returns the base domain path for this version manager. // Example: "gno.land/r/gnoswap/protocol_fee" // // Returns: // - string: Base package path used to scope registered version implementations. func (vm *versionManager) GetDomainPath() string { return vm.domainPath } // GetInitializers returns the map containing all registered initializer functions. // Keys are package paths, values are initializer functions. // Useful for inspecting which versions are available. // // Returns: // - map[string]func(_ int, rlm realm, store any) any: Current registry mapping version package paths to initializer callbacks. func (vm *versionManager) GetInitializers() map[string]func(_ int, rlm realm, store any) any { return vm.initializers } // GetCurrentPackagePath returns the package path of the currently active implementation. // // Returns: // - string: Active implementation's package path, or the empty string before registration. func (vm *versionManager) GetCurrentPackagePath() string { return vm.currentPackagePath } // GetCurrentImplementation returns the instance of the currently active version. // The returned value should be type-asserted to the domain-specific interface. // // Returns: // - any: Active version implementation instance, or nil before the first initializer is registered. func (vm *versionManager) GetCurrentImplementation() any { return vm.currentImplementation } // isContainDomainPath checks if the calling contract is within the authorized domain path. // This is a critical security check that prevents unauthorized external contracts from // registering implementations. // // Validation rules: // - Package path must start with domainPath + "/" // // Example: // - domainPath: "gno.land/r/gnoswap/protocol_fee" // - Valid callers: "gno.land/r/gnoswap/protocol_fee/v1", "gno.land/r/gnoswap/protocol_fee/v2" // - Invalid callers: "gno.land/r/gnoswap/other", "gno.land/r/attacker/malicious" func (vm *versionManager) isContainDomainPath(targetPackagePath string) bool { // `domainPath` is set via the current realm's PkgPath in each contract. // Therefore, there is no need for a separate trailing slash check, // and the prefix is determined by directly appending `/` for version detection. prefix := vm.domainPath + "/" return strings.HasPrefix(targetPackagePath, prefix) } // 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 func NewVersionManager( domainPath string, kvStore store.KVStore, initializeDomainStoreFn func(_ int, rlm realm, kvStore store.KVStore) any, ) VersionManager { return &versionManager{ domainPath: domainPath, domainKVStore: kvStore, initializeDomainStoreFn: initializeDomainStoreFn, initializers: make(map[string]func(_ int, rlm realm, store any) any), currentPackagePath: "", currentImplementation: nil, } }
  12. #12/gno.MemPackageType
  13. #13 MPUserAll

Result log

msg:0,success:true,log:,events:[]

← Back to block 77,243