From 6678c480db0fefe10f50dfd730077a9ce5251936 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 18 Jun 2026 09:45:22 -0600 Subject: [PATCH 01/36] feat(registry): add multi-signer authorization controls (Phase 1 PoC) - Add 4 new RegistryRole enum values: LIEN_OWNER, SECURED_PARTY_FOR_LIEN, SECURED_PARTY_FOR_ENOTE, PLEDGEE - Change MsgGrantRole/MsgRevokeRole from single signer to repeated signers - Add MsgSetRoles/RoleUpdate for atomic multi-role updates - Add authorization.proto defining SignatureType, NftRole, Assignment enums and RoleAuthorization, Authorization, SignatureRequirement, RoleAssignment, RolePriority, RolePriorityEntry message types - Add types/authorization.go with static role authorization policies: DefaultRoleAuthorizations() and RoleAuthorizationMap() - Add keeper/authorization.go with full authorization validation engine: resolveRegistryRoleAddresses, resolveNFTRoleAddresses, resolveRolePriorityAddresses, resolveRoleAssignmentAddresses, evaluateSignatureRequirement, evaluateAuthorization, ValidateRoleChangeAuthorization - Update msg_server.go: GrantRole/RevokeRole use multi-signer auth; add SetRoles handler - Add keeper.go SetRoles method with atomic role updates and diff-based event emission - Update CLI grant-role/revoke-role commands for repeated signers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../registry/v1/authorization.proto | 127 ++ proto/provenance/registry/v1/registry.proto | 11 + proto/provenance/registry/v1/tx.proto | 61 +- x/registry/client/cli/tx.go | 4 +- x/registry/keeper/authorization.go | 208 +++ x/registry/keeper/keeper.go | 50 + x/registry/keeper/msg_server.go | 92 +- x/registry/types/authorization.go | 92 + x/registry/types/authorization.pb.go | 1586 +++++++++++++++++ x/registry/types/msgs.go | 56 +- x/registry/types/msgs_test.go | 70 +- x/registry/types/registry.pb.go | 113 +- x/registry/types/tx.pb.go | 859 ++++++++- 13 files changed, 3161 insertions(+), 168 deletions(-) create mode 100644 proto/provenance/registry/v1/authorization.proto create mode 100644 x/registry/keeper/authorization.go create mode 100644 x/registry/types/authorization.go create mode 100644 x/registry/types/authorization.pb.go diff --git a/proto/provenance/registry/v1/authorization.proto b/proto/provenance/registry/v1/authorization.proto new file mode 100644 index 0000000000..46404f4a37 --- /dev/null +++ b/proto/provenance/registry/v1/authorization.proto @@ -0,0 +1,127 @@ +syntax = "proto3"; +package provenance.registry.v1; + +option go_package = "github.com/provenance-io/provenance/x/registry/types"; +option java_package = "io.provenance.registry.v1"; +option java_multiple_files = true; + +import "provenance/registry/v1/registry.proto"; + +// SignatureType defines how signature requirements are evaluated. +enum SignatureType { + // SIGNATURE_TYPE_UNSPECIFIED is the default/unset value. + SIGNATURE_TYPE_UNSPECIFIED = 0; + // SIGNATURE_TYPE_REQUIRED_ALL requires signatures from ALL listed roles. + // Fails immediately if any role does not exist in the registry or NFT state. + SIGNATURE_TYPE_REQUIRED_ALL = 1; + // SIGNATURE_TYPE_REQUIRED_ALL_IF_SET requires signatures from ALL listed roles, + // but only if they exist. Roles that are not set are silently skipped. + SIGNATURE_TYPE_REQUIRED_ALL_IF_SET = 2; + // SIGNATURE_TYPE_REQUIRED_ANY requires a signature from AT LEAST ONE of the listed roles. + // Fails if none of the roles exist. + SIGNATURE_TYPE_REQUIRED_ANY = 3; + // SIGNATURE_TYPE_REQUIRED_ANY_IF_SET requires a signature from AT LEAST ONE of the listed roles, + // but only if any of them exist. Skips the entire requirement if none of the roles are set. + SIGNATURE_TYPE_REQUIRED_ANY_IF_SET = 4; +} + +// NftRole identifies roles managed outside the registry module (e.g., by the metadata module). +// These roles are read-only from the registry module's perspective. +enum NftRole { + // NFT_ROLE_UNSPECIFIED is the default/unset value. + NFT_ROLE_UNSPECIFIED = 0; + // NFT_ROLE_NFT_OWNER is the owner of the NFT (value owner from the metadata module scope). + NFT_ROLE_NFT_OWNER = 1; +} + +// Assignment describes which address(es) to resolve for a role in an authorization check. +enum Assignment { + // ASSIGNMENT_UNSPECIFIED is the default/unset value. + ASSIGNMENT_UNSPECIFIED = 0; + // ASSIGNMENT_CURRENT resolves exactly one address currently assigned to the role. + // Fails if the role has multiple addresses assigned. + ASSIGNMENT_CURRENT = 1; + // ASSIGNMENT_CURRENT_ALL resolves all addresses currently assigned to the role. + // All of them must sign. + ASSIGNMENT_CURRENT_ALL = 2; + // ASSIGNMENT_CURRENT_ANY resolves any one of the addresses currently assigned to the role. + // At least one must sign. + ASSIGNMENT_CURRENT_ANY = 3; + // ASSIGNMENT_NEW resolves exactly one new address being assigned to the role. + // Fails if multiple new addresses are provided. + // Invalid for NftRole (read-only from registry perspective). + ASSIGNMENT_NEW = 4; + // ASSIGNMENT_NEW_ANY resolves any one of the new addresses being assigned to the role. + // Invalid for NftRole. + ASSIGNMENT_NEW_ANY = 5; + // ASSIGNMENT_NEW_ALL resolves all new addresses being assigned to the role. + // All of them must sign. + // Invalid for NftRole. + ASSIGNMENT_NEW_ALL = 6; +} + +// RoleAuthorization configures who must sign to update a specific role. +// Contains one or more Authorization paths; the update is approved if ANY path is fully satisfied. +message RoleAuthorization { + // role is the registry role whose assignments are being controlled. + RegistryRole role = 1; + + // authorizations is a list of alternative approval paths. + // The role update is approved if any single authorization is fully satisfied. + repeated Authorization authorizations = 2; +} + +// Authorization defines one complete approval path for a role update. +// All signature requirements in this authorization must be satisfied for this path to succeed. +message Authorization { + // description is a human-readable explanation of this authorization path. + string description = 1; + + // signatures is the ordered list of signature requirements that must all be satisfied. + repeated SignatureRequirement signatures = 2; +} + +// SignatureRequirement defines a single signature check within an authorization path. +message SignatureRequirement { + // type controls how the roles are evaluated (required_all, required_all_if_set, etc.). + SignatureType type = 1; + + // roles lists the role assignments that must be checked. + repeated RoleAssignment roles = 2; +} + +// RoleAssignment specifies which role and which assignment type to resolve for a signature check. +message RoleAssignment { + // role_selector identifies the role to resolve, either a single role or a priority list. + oneof role_selector { + // registry_role is a role managed within the registry module state. + // Can be used with any assignment type. + RegistryRole registry_role = 1; + // nft_role is a role managed by another module (e.g., NFT_ROLE_NFT_OWNER from metadata). + // Can only be used with ASSIGNMENT_CURRENT* variants. + NftRole nft_role = 2; + // role_priority is an ordered list of roles; the first one that exists is used. + RolePriority role_priority = 3; + } + + // assignment specifies which address(es) to resolve for the role. + Assignment assignment = 4; +} + +// RolePriority is an ordered list of role entries used for fallback/hierarchical authority. +// The keeper uses the first role in the list that exists. +message RolePriority { + // entries is the ordered list of roles to check. The first existing role is used. + repeated RolePriorityEntry entries = 1; +} + +// RolePriorityEntry is a single entry in a RolePriority list. +message RolePriorityEntry { + // role identifies the role in this priority slot. + oneof role { + // registry_role is a role managed within the registry module state. + RegistryRole registry_role = 1; + // nft_role is a role managed by another module. + NftRole nft_role = 2; + } +} diff --git a/proto/provenance/registry/v1/registry.proto b/proto/provenance/registry/v1/registry.proto index f52119ab40..6bee34bba4 100644 --- a/proto/provenance/registry/v1/registry.proto +++ b/proto/provenance/registry/v1/registry.proto @@ -32,6 +32,17 @@ enum RegistryRole { // REGISTRY_ROLE_ORIGINATOR indicates the address has originator privileges. // Originators are responsible for creating and originating the underlying assets. REGISTRY_ROLE_ORIGINATOR = 6; + // REGISTRY_ROLE_LIEN_OWNER indicates the entity on whose behalf the registry acts as a nominal lien holder. + REGISTRY_ROLE_LIEN_OWNER = 7; + // REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN indicates an organization with a security interest in the lien. + // This party can foreclose into the Lien Owner role or direct a transfer in case of default. + REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN = 8; + // REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE indicates an organization with a financial interest in the eNote. + // This party can foreclose into the Controller role or direct a transfer in case of default. + REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE = 9; + // REGISTRY_ROLE_PLEDGEE indicates a lender or creditor that holds a security interest in pledged loans + // as collateral securing an obligation of the Pledgor. + REGISTRY_ROLE_PLEDGEE = 10; } // RegistryKey represents a unique identifier for registry entries. diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index fb26230b5f..da7a894685 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -9,6 +9,7 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; import "provenance/registry/v1/registry.proto"; +import "provenance/registry/v1/authorization.proto"; // Msg defines the registry Msg service. // This service provides transaction functionality for managing registry entries and roles. @@ -21,10 +22,12 @@ service Msg { // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. + // Multiple signers are supported for multi-party authorization workflows. rpc GrantRole(MsgGrantRole) returns (MsgGrantRoleResponse); // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. + // Multiple signers are supported for multi-party authorization workflows. rpc RevokeRole(MsgRevokeRole) returns (MsgRevokeRoleResponse); // UnregisterNFT unregisters an NFT from the registry. @@ -35,6 +38,10 @@ service Msg { // This creates multiple registry entries, or updates if one exists. // Each registry in this will cost one MsgRegisterNFT. rpc RegistryBulkUpdate(MsgRegistryBulkUpdate) returns (MsgRegistryBulkUpdateResponse); + + // SetRoles atomically sets the desired state for one or more roles on a registry entry. + // Multiple signers are supported for multi-party authorization workflows. + rpc SetRoles(MsgSetRoles) returns (MsgSetRolesResponse); } // MsgRegisterNFT represents a message to register a new NFT in the registry. @@ -61,12 +68,14 @@ message MsgRegisterNFTResponse {} // MsgGrantRole represents a message to grant a role to one or more addresses. // This message adds the specified addresses to an existing role for the given registry key. +// Multiple signers enable multi-party authorization workflows. message MsgGrantRole { - option (cosmos.msg.v1.signer) = "signer"; + option (cosmos.msg.v1.signer) = "signers"; - // signer is the address that is authorized to grant the role. - // This address must have the appropriate permissions to modify role assignments. - string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // signers are the addresses authorizing the role grant. + // When a RegistryClass authorization policy is defined for the target role, all required + // role holders must be included here. Without a policy, a single NFT owner address is required. + repeated string signers = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // key is the registry key to grant the role to. // This identifies the specific registry entry to modify. @@ -87,12 +96,14 @@ message MsgGrantRoleResponse {} // MsgRevokeRole represents a message to revoke a role from one or more addresses. // This message removes the specified addresses from an existing role for the given registry key. +// Multiple signers enable multi-party authorization workflows. message MsgRevokeRole { - option (cosmos.msg.v1.signer) = "signer"; + option (cosmos.msg.v1.signer) = "signers"; - // signer is the address that is authorized to revoke the role. - // This address must have the appropriate permissions to modify role assignments. - string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // signers are the addresses authorizing the role revocation. + // When a RegistryClass authorization policy is defined for the target role, all required + // role holders must be included here. Without a policy, a single NFT owner address is required. + repeated string signers = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // key is the registry key to revoke the role from. // This identifies the specific registry entry to modify. @@ -145,4 +156,36 @@ message MsgRegistryBulkUpdate { // MsgRegistryBulkUpdateResponse defines the response for RegistryBulkUpdate. // This is an empty response indicating successful bulk update. -message MsgRegistryBulkUpdateResponse {} \ No newline at end of file +message MsgRegistryBulkUpdateResponse {} + +// MsgSetRoles atomically sets the desired state for one or more roles on a registry entry. +// For each role in role_updates, the keeper computes the diff between current and desired +// addresses and applies grants and revokes to reach the desired state. +// All updates succeed or all fail (atomic). +message MsgSetRoles { + option (cosmos.msg.v1.signer) = "signers"; + + // signers are the addresses authorizing the role updates. + // All required role holders per the RegistryClass authorization policy must be present. + repeated string signers = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // key identifies the registry entry to update. + RegistryKey key = 2; + + // role_updates contains the desired state for each role to update. + // At least one entry is required. Roles not listed here are unchanged. + repeated RoleUpdate role_updates = 3 [(gogoproto.nullable) = false]; +} + +// RoleUpdate specifies the desired set of addresses for a single role. +message RoleUpdate { + // role is the role to update. + RegistryRole role = 1; + + // addresses is the desired set of addresses for this role. + // An empty list means the role should be cleared entirely. + repeated string addresses = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgSetRolesResponse defines the response for SetRoles. +message MsgSetRolesResponse {} \ No newline at end of file diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index dd522d0189..c28ac25a4e 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -83,7 +83,7 @@ func CmdGrantRole() *cobra.Command { } msg := types.MsgGrantRole{ - Signer: clientCtx.GetFromAddress().String(), + Signers: []string{clientCtx.GetFromAddress().String()}, Key: &types.RegistryKey{ AssetClassId: args[0], NftId: args[1], @@ -120,7 +120,7 @@ func CmdRevokeRole() *cobra.Command { } msg := types.MsgRevokeRole{ - Signer: clientCtx.GetFromAddress().String(), + Signers: []string{clientCtx.GetFromAddress().String()}, Key: &types.RegistryKey{ AssetClassId: args[0], NftId: args[1], diff --git a/x/registry/keeper/authorization.go b/x/registry/keeper/authorization.go new file mode 100644 index 0000000000..ced2ae04b3 --- /dev/null +++ b/x/registry/keeper/authorization.go @@ -0,0 +1,208 @@ +package keeper + +import ( + "context" + "fmt" + "strings" + + "github.com/provenance-io/provenance/x/registry/types" +) + +// resolveRegistryRoleAddresses returns the addresses currently held for a given registry role +// (ASSIGNMENT_CURRENT variants) or the incoming new addresses (ASSIGNMENT_NEW variants). +func (k Keeper) resolveRegistryRoleAddresses(entry *types.RegistryEntry, role types.RegistryRole, assignment types.Assignment, newAddrs []string) ([]string, bool) { + switch assignment { + case types.Assignment_ASSIGNMENT_CURRENT, + types.Assignment_ASSIGNMENT_CURRENT_ALL, + types.Assignment_ASSIGNMENT_CURRENT_ANY: + for _, re := range entry.Roles { + if re.Role == role { + return re.Addresses, len(re.Addresses) > 0 + } + } + return nil, false + case types.Assignment_ASSIGNMENT_NEW, + types.Assignment_ASSIGNMENT_NEW_ALL, + types.Assignment_ASSIGNMENT_NEW_ANY: + return newAddrs, len(newAddrs) > 0 + default: + return nil, false + } +} + +// resolveNFTRoleAddresses returns the address(es) for an NFT-module role (e.g. the NFT owner). +func (k Keeper) resolveNFTRoleAddresses(ctx context.Context, entry *types.RegistryEntry, nftRole types.NftRole) ([]string, bool) { + switch nftRole { + case types.NftRole_NFT_ROLE_NFT_OWNER: + owner := k.GetNFTOwner(ctx, &entry.Key.AssetClassId, &entry.Key.NftId) + if len(owner) == 0 { + return nil, false + } + return []string{owner.String()}, true + default: + return nil, false + } +} + +// resolveRolePriorityAddresses iterates priority entries and returns addresses from the first +// non-empty role that exists, implementing the "first match wins" fallback chain. +func (k Keeper) resolveRolePriorityAddresses(ctx context.Context, entry *types.RegistryEntry, rp *types.RolePriority, assignment types.Assignment, newAddrs []string) ([]string, bool) { + if rp == nil { + return nil, false + } + for _, e := range rp.Entries { + switch r := e.Role.(type) { + case *types.RolePriorityEntry_RegistryRole: + addrs, exists := k.resolveRegistryRoleAddresses(entry, r.RegistryRole, assignment, newAddrs) + if exists { + return addrs, true + } + case *types.RolePriorityEntry_NftRole: + addrs, exists := k.resolveNFTRoleAddresses(ctx, entry, r.NftRole) + if exists { + return addrs, true + } + } + } + return nil, false +} + +// resolveRoleAssignmentAddresses dispatches to the correct resolver based on the role selector type. +func (k Keeper) resolveRoleAssignmentAddresses(ctx context.Context, entry *types.RegistryEntry, ra *types.RoleAssignment, newAddrs []string) ([]string, bool) { + if ra == nil { + return nil, false + } + switch r := ra.RoleSelector.(type) { + case *types.RoleAssignment_RegistryRole: + return k.resolveRegistryRoleAddresses(entry, r.RegistryRole, ra.Assignment, newAddrs) + case *types.RoleAssignment_NftRole: + return k.resolveNFTRoleAddresses(ctx, entry, r.NftRole) + case *types.RoleAssignment_RolePriority: + return k.resolveRolePriorityAddresses(ctx, entry, r.RolePriority, ra.Assignment, newAddrs) + default: + return nil, false + } +} + +// evaluateSignatureRequirement checks whether a single SignatureRequirement is satisfied. +// +// - REQUIRED_ALL: every resolved address from every role must be in signerSet. +// - REQUIRED_ALL_IF_SET: for each role that resolves to non-empty addresses, all must sign; +// roles that resolve to empty are skipped. +// - REQUIRED_ANY: at least one address from any role must be in signerSet. +// - REQUIRED_ANY_IF_SET: same as REQUIRED_ANY but empty roles are skipped. +func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.RegistryEntry, req *types.SignatureRequirement, newAddrs []string, signerSet map[string]bool) error { + if req == nil { + return nil + } + switch req.Type { + case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL: + for _, ra := range req.Roles { + addrs, _ := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + for _, addr := range addrs { + if !signerSet[addr] { + return types.NewErrCodeUnauthorized(fmt.Sprintf("missing required signature for %q", addr)) + } + } + } + + case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET: + for _, ra := range req.Roles { + addrs, exists := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if !exists || len(addrs) == 0 { + continue + } + for _, addr := range addrs { + if !signerSet[addr] { + return types.NewErrCodeUnauthorized(fmt.Sprintf("missing required signature for %q", addr)) + } + } + } + + case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY: + found := false + for _, ra := range req.Roles { + addrs, _ := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + for _, addr := range addrs { + if signerSet[addr] { + found = true + break + } + } + if found { + break + } + } + if !found { + return types.NewErrCodeUnauthorized("no required signature found in any role") + } + + case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY_IF_SET: + hasAnyRole := false + found := false + for _, ra := range req.Roles { + addrs, exists := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if !exists || len(addrs) == 0 { + continue + } + hasAnyRole = true + for _, addr := range addrs { + if signerSet[addr] { + found = true + break + } + } + if found { + break + } + } + if hasAnyRole && !found { + return types.NewErrCodeUnauthorized("no required signature found in any role") + } + } + return nil +} + +// evaluateAuthorization checks whether every SignatureRequirement in an authorization path is +// satisfied. Returns nil if all requirements pass, or an error describing the first failure. +func (k Keeper) evaluateAuthorization(ctx context.Context, entry *types.RegistryEntry, auth *types.Authorization, newAddrs []string, signerSet map[string]bool) error { + if auth == nil { + return nil + } + for i, req := range auth.Signatures { + if err := k.evaluateSignatureRequirement(ctx, entry, req, newAddrs, signerSet); err != nil { + return fmt.Errorf("signature requirement %d: %w", i, err) + } + } + return nil +} + +// ValidateRoleChangeAuthorization validates that the provided signers are authorized to update a +// role on the given entry. It tries each authorization path in the RoleAuthorization; the +// operation is approved as soon as any single path is fully satisfied. +// +// newAddrs contains the addresses being assigned to the role (used for ASSIGNMENT_NEW checks). +// For revoke operations, newAddrs should be nil or empty. +func (k Keeper) ValidateRoleChangeAuthorization(ctx context.Context, roleAuth types.RoleAuthorization, entry *types.RegistryEntry, newAddrs []string, signers []string) error { + signerSet := make(map[string]bool, len(signers)) + for _, s := range signers { + signerSet[s] = true + } + + var pathErrors []string + for _, auth := range roleAuth.Authorizations { + if err := k.evaluateAuthorization(ctx, entry, auth, newAddrs, signerSet); err == nil { + return nil + } else { + desc := auth.Description + if desc == "" { + desc = fmt.Sprintf("path %d", len(pathErrors)+1) + } + pathErrors = append(pathErrors, fmt.Sprintf("%s: %v", desc, err)) + } + } + + return types.NewErrCodeUnauthorized( + fmt.Sprintf("no valid authorization path found for %s: %s", roleAuth.Role.ShortString(), strings.Join(pathErrors, "; ")), + ) +} diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index facd719377..c3fb48cd40 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -286,6 +286,56 @@ func (k Keeper) SetRegistry(ctx context.Context, entry types.RegistryEntry) erro return k.Registry.Set(ctx, entry.Key.CollKey(), entry) } +// SetRoles atomically sets the desired state for one or more roles on a registry entry. +// Each RoleUpdate specifies a role and the complete desired set of addresses; an empty +// address list clears the role entirely. +func (k Keeper) SetRoles(ctx context.Context, key *types.RegistryKey, roleUpdates []types.RoleUpdate) error { + entry, err := k.Registry.Get(ctx, key.CollKey()) + if err != nil { + return types.NewErrCodeRegistryNotFound(key.String()) + } + + // Snapshot the original entry to compute diff events. + origCopy := entry + + for _, update := range roleUpdates { + roleI := -1 + for i, re := range entry.Roles { + if re.Role == update.Role { + roleI = i + break + } + } + + if len(update.Addresses) == 0 { + // Clear the role if present. + if roleI >= 0 { + entry.Roles = append(entry.Roles[:roleI], entry.Roles[roleI+1:]...) + } + } else { + // Set the complete desired address list for the role. + if roleI >= 0 { + entry.Roles[roleI].Addresses = update.Addresses + } else { + entry.Roles = append(entry.Roles, types.RolesEntry{Role: update.Role, Addresses: update.Addresses}) + } + } + } + + if err = k.Registry.Set(ctx, key.CollKey(), entry); err != nil { + return fmt.Errorf("failed to set registry entry: %w", err) + } + + grantEvents, revokeEvents := types.GetChangeEvents(&origCopy, &entry) + for _, tev := range grantEvents { + k.EmitEvent(ctx, tev) + } + for _, tev := range revokeEvents { + k.EmitEvent(ctx, tev) + } + return nil +} + // GetRegistries returns the registries paginated func (k Keeper) GetRegistries(ctx context.Context, pagination *query.PageRequest, assetClassID string) ([]types.RegistryEntry, *query.PageResponse, error) { var opts []func(opt *query.CollectionsPaginateOptions[collections.Pair[string, string]]) diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 85ae849ead..65b1b7e082 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -46,18 +46,33 @@ func (k msgServer) RegisterNFT(ctx context.Context, msg *types.MsgRegisterNFT) ( // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*types.MsgGrantRoleResponse, error) { - // ensure the registry exists - if err := k.ValidateRegistryExists(ctx, msg.Key); err != nil { + // Ensure the registry exists and fetch the current entry for auth checks. + entry, err := k.GetRegistry(ctx, msg.Key) + if err != nil { return nil, err } + if entry == nil { + return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) + } - // Validate that the signer owns the NFT - if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { - return nil, err + roleAuths := types.RoleAuthorizationMap() + if roleAuth, ok := roleAuths[msg.Role]; ok { + // Policy-based multi-signer path: the incoming addresses are the "new" assignment. + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, msg.Addresses, msg.Signers); err != nil { + return nil, err + } + } else { + // Legacy fallback: first signer must own the NFT. + if len(msg.Signers) == 0 { + return nil, types.NewErrCodeUnauthorized("no signers provided") + } + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signers[0]); err != nil { + return nil, err + } } // Grant the role to the addresses. - err := k.Keeper.GrantRole(ctx, msg.Key, msg.Role, msg.Addresses) + err = k.Keeper.GrantRole(ctx, msg.Key, msg.Role, msg.Addresses) if err != nil { return nil, err } @@ -68,18 +83,33 @@ func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*typ // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*types.MsgRevokeRoleResponse, error) { - // ensure the registry exists - if err := k.ValidateRegistryExists(ctx, msg.Key); err != nil { + // Ensure the registry exists and fetch the current entry for auth checks. + entry, err := k.GetRegistry(ctx, msg.Key) + if err != nil { return nil, err } + if entry == nil { + return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) + } - // Validate that the signer owns the NFT - if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { - return nil, err + roleAuths := types.RoleAuthorizationMap() + if roleAuth, ok := roleAuths[msg.Role]; ok { + // Policy-based multi-signer path. For revoke, no new addresses are being assigned. + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, nil, msg.Signers); err != nil { + return nil, err + } + } else { + // Legacy fallback: first signer must own the NFT. + if len(msg.Signers) == 0 { + return nil, types.NewErrCodeUnauthorized("no signers provided") + } + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signers[0]); err != nil { + return nil, err + } } // Revoke the role from the addresses. - err := k.Keeper.RevokeRole(ctx, msg.Key, msg.Role, msg.Addresses) + err = k.Keeper.RevokeRole(ctx, msg.Key, msg.Role, msg.Addresses) if err != nil { return nil, err } @@ -87,6 +117,44 @@ func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*t return &types.MsgRevokeRoleResponse{}, nil } +// SetRoles atomically sets the desired state for one or more roles on a registry entry. +// Each role update specifies a role and the complete desired set of addresses for that role. +// An empty address list clears the role. +func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types.MsgSetRolesResponse, error) { + // Ensure the registry exists and fetch the current entry for auth checks. + entry, err := k.GetRegistry(ctx, msg.Key) + if err != nil { + return nil, err + } + if entry == nil { + return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) + } + + roleAuths := types.RoleAuthorizationMap() + for _, update := range msg.RoleUpdates { + if roleAuth, ok := roleAuths[update.Role]; ok { + // The new addresses for this role are the desired state from the update. + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, update.Addresses, msg.Signers); err != nil { + return nil, err + } + } else { + // Legacy fallback: first signer must own the NFT. + if len(msg.Signers) == 0 { + return nil, types.NewErrCodeUnauthorized("no signers provided") + } + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signers[0]); err != nil { + return nil, err + } + } + } + + if err := k.Keeper.SetRoles(ctx, msg.Key, msg.RoleUpdates); err != nil { + return nil, err + } + + return &types.MsgSetRolesResponse{}, nil +} + // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry and associated data for the specified key. func (k msgServer) UnregisterNFT(ctx context.Context, msg *types.MsgUnregisterNFT) (*types.MsgUnregisterNFTResponse, error) { diff --git a/x/registry/types/authorization.go b/x/registry/types/authorization.go new file mode 100644 index 0000000000..7fbc06c07e --- /dev/null +++ b/x/registry/types/authorization.go @@ -0,0 +1,92 @@ +package types + +// DefaultRoleAuthorizations returns the static role authorization policies. +// These policies encode who must sign to change each participant role. +// In a future phase, these will be replaced by per-RegistryClass dynamic policies. +func DefaultRoleAuthorizations() []RoleAuthorization { + return []RoleAuthorization{ + controllerRoleAuthorization(), + } +} + +// RoleAuthorizationMap returns a map of RegistryRole → RoleAuthorization for fast lookup. +func RoleAuthorizationMap() map[RegistryRole]RoleAuthorization { + auths := DefaultRoleAuthorizations() + m := make(map[RegistryRole]RoleAuthorization, len(auths)) + for _, a := range auths { + m[a.Role] = a + } + return m +} + +// controllerRoleAuthorization defines the CONTROLLER role authorization policy. +// +// Path 1 – Standard transfer: The current controller (or NFT owner if no controller is set) must +// sign. Additionally, the current Secured Party for eNote and the incoming new controller must +// each sign if they are set. +// +// Path 2 – Foreclosure: The current Secured Party for eNote and the new controller both sign, +// allowing the secured party to unilaterally assume control in case of default. +func controllerRoleAuthorization() RoleAuthorization { + return RoleAuthorization{ + Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, + Authorizations: []*Authorization{ + { + Description: "Transfer requiring current controller approval", + Signatures: []*SignatureRequirement{ + { + // The current authority (controller if set, otherwise NFT owner) must sign. + Type: SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + Roles: []*RoleAssignment{ + { + RoleSelector: &RoleAssignment_RolePriority{ + RolePriority: &RolePriority{ + Entries: []*RolePriorityEntry{ + {Role: &RolePriorityEntry_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_CONTROLLER}}, + {Role: &RolePriorityEntry_NftRole{NftRole: NftRole_NFT_ROLE_NFT_OWNER}}, + }, + }, + }, + Assignment: Assignment_ASSIGNMENT_CURRENT, + }, + }, + }, + { + // The current Secured Party for eNote must sign (if set). + // The new/incoming controller must sign (if being assigned). + Type: SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET, + Roles: []*RoleAssignment{ + { + RoleSelector: &RoleAssignment_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE}, + Assignment: Assignment_ASSIGNMENT_CURRENT, + }, + { + RoleSelector: &RoleAssignment_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_CONTROLLER}, + Assignment: Assignment_ASSIGNMENT_NEW, + }, + }, + }, + }, + }, + { + Description: "Foreclosure: Secured Party for eNote can unilaterally become Controller in case of default", + Signatures: []*SignatureRequirement{ + { + // The Secured Party for eNote and the incoming new controller must both sign. + Type: SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + Roles: []*RoleAssignment{ + { + RoleSelector: &RoleAssignment_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE}, + Assignment: Assignment_ASSIGNMENT_CURRENT, + }, + { + RoleSelector: &RoleAssignment_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_CONTROLLER}, + Assignment: Assignment_ASSIGNMENT_NEW, + }, + }, + }, + }, + }, + }, + } +} diff --git a/x/registry/types/authorization.pb.go b/x/registry/types/authorization.pb.go new file mode 100644 index 0000000000..6a78c39b82 --- /dev/null +++ b/x/registry/types/authorization.pb.go @@ -0,0 +1,1586 @@ +// Manually written to match protoc-gen-gogo output for provenance/registry/v1/authorization.proto. +// Should be regenerated with protoc when the proto builder is available. + +package types + +import ( + "fmt" + io "io" + math_bits "math/bits" + + proto "github.com/cosmos/gogoproto/proto" +) + +// SignatureType defines how signature requirements are evaluated. +type SignatureType int32 + +const ( + SignatureType_SIGNATURE_TYPE_UNSPECIFIED SignatureType = 0 + SignatureType_SIGNATURE_TYPE_REQUIRED_ALL SignatureType = 1 + SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET SignatureType = 2 + SignatureType_SIGNATURE_TYPE_REQUIRED_ANY SignatureType = 3 + SignatureType_SIGNATURE_TYPE_REQUIRED_ANY_IF_SET SignatureType = 4 +) + +var SignatureType_name = map[int32]string{ + 0: "SIGNATURE_TYPE_UNSPECIFIED", + 1: "SIGNATURE_TYPE_REQUIRED_ALL", + 2: "SIGNATURE_TYPE_REQUIRED_ALL_IF_SET", + 3: "SIGNATURE_TYPE_REQUIRED_ANY", + 4: "SIGNATURE_TYPE_REQUIRED_ANY_IF_SET", +} + +var SignatureType_value = map[string]int32{ + "SIGNATURE_TYPE_UNSPECIFIED": 0, + "SIGNATURE_TYPE_REQUIRED_ALL": 1, + "SIGNATURE_TYPE_REQUIRED_ALL_IF_SET": 2, + "SIGNATURE_TYPE_REQUIRED_ANY": 3, + "SIGNATURE_TYPE_REQUIRED_ANY_IF_SET": 4, +} + +func (x SignatureType) String() string { + return proto.EnumName(SignatureType_name, int32(x)) +} + +// NftRole identifies roles managed outside the registry module. +type NftRole int32 + +const ( + NftRole_NFT_ROLE_UNSPECIFIED NftRole = 0 + NftRole_NFT_ROLE_NFT_OWNER NftRole = 1 +) + +var NftRole_name = map[int32]string{ + 0: "NFT_ROLE_UNSPECIFIED", + 1: "NFT_ROLE_NFT_OWNER", +} + +var NftRole_value = map[string]int32{ + "NFT_ROLE_UNSPECIFIED": 0, + "NFT_ROLE_NFT_OWNER": 1, +} + +func (x NftRole) String() string { + return proto.EnumName(NftRole_name, int32(x)) +} + +// Assignment describes which address(es) to resolve for a role. +type Assignment int32 + +const ( + Assignment_ASSIGNMENT_UNSPECIFIED Assignment = 0 + Assignment_ASSIGNMENT_CURRENT Assignment = 1 + Assignment_ASSIGNMENT_CURRENT_ALL Assignment = 2 + Assignment_ASSIGNMENT_CURRENT_ANY Assignment = 3 + Assignment_ASSIGNMENT_NEW Assignment = 4 + Assignment_ASSIGNMENT_NEW_ANY Assignment = 5 + Assignment_ASSIGNMENT_NEW_ALL Assignment = 6 +) + +var Assignment_name = map[int32]string{ + 0: "ASSIGNMENT_UNSPECIFIED", + 1: "ASSIGNMENT_CURRENT", + 2: "ASSIGNMENT_CURRENT_ALL", + 3: "ASSIGNMENT_CURRENT_ANY", + 4: "ASSIGNMENT_NEW", + 5: "ASSIGNMENT_NEW_ANY", + 6: "ASSIGNMENT_NEW_ALL", +} + +var Assignment_value = map[string]int32{ + "ASSIGNMENT_UNSPECIFIED": 0, + "ASSIGNMENT_CURRENT": 1, + "ASSIGNMENT_CURRENT_ALL": 2, + "ASSIGNMENT_CURRENT_ANY": 3, + "ASSIGNMENT_NEW": 4, + "ASSIGNMENT_NEW_ANY": 5, + "ASSIGNMENT_NEW_ALL": 6, +} + +func (x Assignment) String() string { + return proto.EnumName(Assignment_name, int32(x)) +} + +func init() { + proto.RegisterEnum("provenance.registry.v1.SignatureType", SignatureType_name, SignatureType_value) + proto.RegisterEnum("provenance.registry.v1.NftRole", NftRole_name, NftRole_value) + proto.RegisterEnum("provenance.registry.v1.Assignment", Assignment_name, Assignment_value) + proto.RegisterType((*RoleAuthorization)(nil), "provenance.registry.v1.RoleAuthorization") + proto.RegisterType((*Authorization)(nil), "provenance.registry.v1.Authorization") + proto.RegisterType((*SignatureRequirement)(nil), "provenance.registry.v1.SignatureRequirement") + proto.RegisterType((*RoleAssignment)(nil), "provenance.registry.v1.RoleAssignment") + proto.RegisterType((*RolePriority)(nil), "provenance.registry.v1.RolePriority") + proto.RegisterType((*RolePriorityEntry)(nil), "provenance.registry.v1.RolePriorityEntry") +} + +// ----------------------------------------------------------------------- +// RoleAuthorization +// ----------------------------------------------------------------------- + +// RoleAuthorization configures who must sign to update a specific role. +type RoleAuthorization struct { + // role is the registry role being controlled. + Role RegistryRole `protobuf:"varint,1,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` + // authorizations is a list of alternative approval paths. + Authorizations []Authorization `protobuf:"bytes,2,rep,name=authorizations,proto3" json:"authorizations"` +} + +func (m *RoleAuthorization) Reset() { *m = RoleAuthorization{} } +func (m *RoleAuthorization) String() string { return proto.CompactTextString(m) } +func (*RoleAuthorization) ProtoMessage() {} +func (m *RoleAuthorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (m *RoleAuthorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RoleAuthorization.Marshal(b, m, deterministic) + } + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *RoleAuthorization) XXX_Merge(src proto.Message) { xxx_messageInfo_RoleAuthorization.Merge(m, src) } +func (m *RoleAuthorization) XXX_Size() int { return m.Size() } +func (m *RoleAuthorization) XXX_DiscardUnknown() { xxx_messageInfo_RoleAuthorization.DiscardUnknown(m) } + +var xxx_messageInfo_RoleAuthorization proto.InternalMessageInfo + +func (m *RoleAuthorization) GetRole() RegistryRole { + if m != nil { + return m.Role + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RoleAuthorization) GetAuthorizations() []Authorization { + if m != nil { + return m.Authorizations + } + return nil +} + +func (m *RoleAuthorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleAuthorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAuthorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Authorizations) > 0 { + for iNdEx := len(m.Authorizations) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Authorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + } + if m.Role != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.Role)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *RoleAuthorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Role != 0 { + n += 1 + sovAuthorization(uint64(m.Role)) + } + for _, e := range m.Authorizations { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + return n +} + +func (m *RoleAuthorization) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoleAuthorization: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoleAuthorization: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + m.Role = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Role |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authorizations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authorizations = append(m.Authorizations, Authorization{}) + if err := m.Authorizations[len(m.Authorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +// ----------------------------------------------------------------------- +// Authorization +// ----------------------------------------------------------------------- + +// Authorization defines one complete approval path for a role update. +type Authorization struct { + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + Signatures []SignatureRequirement `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures"` +} + +func (m *Authorization) Reset() { *m = Authorization{} } +func (m *Authorization) String() string { return proto.CompactTextString(m) } +func (*Authorization) ProtoMessage() {} +func (m *Authorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (m *Authorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Authorization.Marshal(b, m, deterministic) + } + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *Authorization) XXX_Merge(src proto.Message) { xxx_messageInfo_Authorization.Merge(m, src) } +func (m *Authorization) XXX_Size() int { return m.Size() } +func (m *Authorization) XXX_DiscardUnknown() { xxx_messageInfo_Authorization.DiscardUnknown(m) } + +var xxx_messageInfo_Authorization proto.InternalMessageInfo + +func (m *Authorization) GetDescription() string { + if m != nil { + return m.Description + } + return "" +} + +func (m *Authorization) GetSignatures() []SignatureRequirement { + if m != nil { + return m.Signatures + } + return nil +} + +func (m *Authorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Authorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Authorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signatures) > 0 { + for iNdEx := len(m.Signatures) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Signatures[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Authorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Description) + if l > 0 { + n += 1 + l + sovAuthorization(uint64(l)) + } + for _, e := range m.Signatures { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + return n +} + +func (m *Authorization) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Authorization: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Authorization: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signatures", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signatures = append(m.Signatures, SignatureRequirement{}) + if err := m.Signatures[len(m.Signatures)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +// ----------------------------------------------------------------------- +// SignatureRequirement +// ----------------------------------------------------------------------- + +// SignatureRequirement defines a single signature check within an authorization path. +type SignatureRequirement struct { + Type SignatureType `protobuf:"varint,1,opt,name=type,proto3,enum=provenance.registry.v1.SignatureType" json:"type,omitempty"` + Roles []RoleAssignment `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles"` +} + +func (m *SignatureRequirement) Reset() { *m = SignatureRequirement{} } +func (m *SignatureRequirement) String() string { return proto.CompactTextString(m) } +func (*SignatureRequirement) ProtoMessage() {} +func (m *SignatureRequirement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (m *SignatureRequirement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignatureRequirement.Marshal(b, m, deterministic) + } + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *SignatureRequirement) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignatureRequirement.Merge(m, src) +} +func (m *SignatureRequirement) XXX_Size() int { return m.Size() } +func (m *SignatureRequirement) XXX_DiscardUnknown() { + xxx_messageInfo_SignatureRequirement.DiscardUnknown(m) +} + +var xxx_messageInfo_SignatureRequirement proto.InternalMessageInfo + +func (m *SignatureRequirement) GetType() SignatureType { + if m != nil { + return m.Type + } + return SignatureType_SIGNATURE_TYPE_UNSPECIFIED +} + +func (m *SignatureRequirement) GetRoles() []RoleAssignment { + if m != nil { + return m.Roles + } + return nil +} + +func (m *SignatureRequirement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignatureRequirement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignatureRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Roles) > 0 { + for iNdEx := len(m.Roles) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Roles[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x12 + } + } + if m.Type != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *SignatureRequirement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovAuthorization(uint64(m.Type)) + } + for _, e := range m.Roles { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + return n +} + +func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignatureRequirement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignatureRequirement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= SignatureType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Roles = append(m.Roles, RoleAssignment{}) + if err := m.Roles[len(m.Roles)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +// ----------------------------------------------------------------------- +// RoleAssignment +// ----------------------------------------------------------------------- + +// RoleAssignment_RoleSelector is the oneof interface for RoleAssignment. +type isRoleAssignment_RoleSelector interface { + isRoleAssignment_RoleSelector() + MarshalTo([]byte) (int, error) + Size() int +} + +type RoleAssignment_RegistryRole struct { + RegistryRole RegistryRole `protobuf:"varint,1,opt,name=registry_role,json=registryRole,proto3,enum=provenance.registry.v1.RegistryRole,oneof"` +} + +type RoleAssignment_NftRole struct { + NftRole NftRole `protobuf:"varint,2,opt,name=nft_role,json=nftRole,proto3,enum=provenance.registry.v1.NftRole,oneof"` +} + +type RoleAssignment_RolePriority struct { + RolePriority *RolePriority `protobuf:"bytes,3,opt,name=role_priority,json=rolePriority,proto3,oneof"` +} + +func (*RoleAssignment_RegistryRole) isRoleAssignment_RoleSelector() {} +func (*RoleAssignment_NftRole) isRoleAssignment_RoleSelector() {} +func (*RoleAssignment_RolePriority) isRoleAssignment_RoleSelector() {} + +func (m *RoleAssignment_RegistryRole) MarshalTo(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.RegistryRole)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *RoleAssignment_NftRole) MarshalTo(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.NftRole)) + i-- + dAtA[i] = 0x10 + return len(dAtA) - i, nil +} + +func (m *RoleAssignment_RolePriority) MarshalTo(dAtA []byte) (int, error) { + i := len(dAtA) + if m.RolePriority != nil { + size, err := m.RolePriority.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + return len(dAtA) - i, nil +} + +func (m *RoleAssignment_RegistryRole) Size() int { + return 1 + sovAuthorization(uint64(m.RegistryRole)) +} + +func (m *RoleAssignment_NftRole) Size() int { + return 1 + sovAuthorization(uint64(m.NftRole)) +} + +func (m *RoleAssignment_RolePriority) Size() int { + if m.RolePriority == nil { + return 1 + sovAuthorization(0) + } + s := m.RolePriority.Size() + return 1 + s + sovAuthorization(uint64(s)) +} + +// RoleAssignment specifies which role and assignment type to resolve for a signature check. +type RoleAssignment struct { + // Types that are valid to be assigned to RoleSelector: + // *RoleAssignment_RegistryRole + // *RoleAssignment_NftRole + // *RoleAssignment_RolePriority + RoleSelector isRoleAssignment_RoleSelector `protobuf_oneof:"role_selector"` + // assignment specifies which address(es) to resolve for the role. + Assignment Assignment `protobuf:"varint,4,opt,name=assignment,proto3,enum=provenance.registry.v1.Assignment" json:"assignment,omitempty"` +} + +func (m *RoleAssignment) Reset() { *m = RoleAssignment{} } +func (m *RoleAssignment) String() string { return proto.CompactTextString(m) } +func (*RoleAssignment) ProtoMessage() {} +func (m *RoleAssignment) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (m *RoleAssignment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RoleAssignment.Marshal(b, m, deterministic) + } + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *RoleAssignment) XXX_Merge(src proto.Message) { xxx_messageInfo_RoleAssignment.Merge(m, src) } +func (m *RoleAssignment) XXX_Size() int { return m.Size() } +func (m *RoleAssignment) XXX_DiscardUnknown() { xxx_messageInfo_RoleAssignment.DiscardUnknown(m) } + +var xxx_messageInfo_RoleAssignment proto.InternalMessageInfo + +func (m *RoleAssignment) GetRoleSelector() isRoleAssignment_RoleSelector { + if m != nil { + return m.RoleSelector + } + return nil +} + +func (m *RoleAssignment) GetRegistryRole() RegistryRole { + if x, ok := m.GetRoleSelector().(*RoleAssignment_RegistryRole); ok { + return x.RegistryRole + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RoleAssignment) GetNftRole() NftRole { + if x, ok := m.GetRoleSelector().(*RoleAssignment_NftRole); ok { + return x.NftRole + } + return NftRole_NFT_ROLE_UNSPECIFIED +} + +func (m *RoleAssignment) GetRolePriority() *RolePriority { + if x, ok := m.GetRoleSelector().(*RoleAssignment_RolePriority); ok { + return x.RolePriority + } + return nil +} + +func (m *RoleAssignment) GetAssignment() Assignment { + if m != nil { + return m.Assignment + } + return Assignment_ASSIGNMENT_UNSPECIFIED +} + +func (m *RoleAssignment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleAssignment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAssignment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Assignment != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.Assignment)) + i-- + dAtA[i] = 0x20 + } + if m.RoleSelector != nil { + n, err := m.RoleSelector.MarshalTo(dAtA[:i]) + if err != nil { + return 0, err + } + i -= n + } + return len(dAtA) - i, nil +} + +func (m *RoleAssignment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.RoleSelector != nil { + n += m.RoleSelector.Size() + } + if m.Assignment != 0 { + n += 1 + sovAuthorization(uint64(m.Assignment)) + } + return n +} + +func (m *RoleAssignment) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoleAssignment: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoleAssignment: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryRole", wireType) + } + var v RegistryRole + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RoleSelector = &RoleAssignment_RegistryRole{RegistryRole: v} + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NftRole", wireType) + } + var v NftRole + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= NftRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.RoleSelector = &RoleAssignment_NftRole{NftRole: v} + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RolePriority", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &RolePriority{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.RoleSelector = &RoleAssignment_RolePriority{RolePriority: v} + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Assignment", wireType) + } + m.Assignment = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Assignment |= Assignment(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +// ----------------------------------------------------------------------- +// RolePriority +// ----------------------------------------------------------------------- + +// RolePriority is an ordered list of role entries; the first existing role is used. +type RolePriority struct { + Entries []RolePriorityEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries"` +} + +func (m *RolePriority) Reset() { *m = RolePriority{} } +func (m *RolePriority) String() string { return proto.CompactTextString(m) } +func (*RolePriority) ProtoMessage() {} +func (m *RolePriority) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (m *RolePriority) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RolePriority.Marshal(b, m, deterministic) + } + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *RolePriority) XXX_Merge(src proto.Message) { xxx_messageInfo_RolePriority.Merge(m, src) } +func (m *RolePriority) XXX_Size() int { return m.Size() } +func (m *RolePriority) XXX_DiscardUnknown() { xxx_messageInfo_RolePriority.DiscardUnknown(m) } + +var xxx_messageInfo_RolePriority proto.InternalMessageInfo + +func (m *RolePriority) GetEntries() []RolePriorityEntry { + if m != nil { + return m.Entries + } + return nil +} + +func (m *RolePriority) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RolePriority) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RolePriority) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + for iNdEx := len(m.Entries) - 1; iNdEx >= 0; iNdEx-- { + size, err := m.Entries[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *RolePriority) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + for _, e := range m.Entries { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + return n +} + +func (m *RolePriority) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RolePriority: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RolePriority: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Entries", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Entries = append(m.Entries, RolePriorityEntry{}) + if err := m.Entries[len(m.Entries)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +// ----------------------------------------------------------------------- +// RolePriorityEntry +// ----------------------------------------------------------------------- + +type isRolePriorityEntry_Role interface { + isRolePriorityEntry_Role() + MarshalTo([]byte) (int, error) + Size() int +} + +type RolePriorityEntry_RegistryRole struct { + RegistryRole RegistryRole `protobuf:"varint,1,opt,name=registry_role,json=registryRole,proto3,enum=provenance.registry.v1.RegistryRole,oneof"` +} + +type RolePriorityEntry_NftRole struct { + NftRole NftRole `protobuf:"varint,2,opt,name=nft_role,json=nftRole,proto3,enum=provenance.registry.v1.NftRole,oneof"` +} + +func (*RolePriorityEntry_RegistryRole) isRolePriorityEntry_Role() {} +func (*RolePriorityEntry_NftRole) isRolePriorityEntry_Role() {} + +func (m *RolePriorityEntry_RegistryRole) MarshalTo(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.RegistryRole)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} + +func (m *RolePriorityEntry_NftRole) MarshalTo(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.NftRole)) + i-- + dAtA[i] = 0x10 + return len(dAtA) - i, nil +} + +func (m *RolePriorityEntry_RegistryRole) Size() int { + return 1 + sovAuthorization(uint64(m.RegistryRole)) +} + +func (m *RolePriorityEntry_NftRole) Size() int { + return 1 + sovAuthorization(uint64(m.NftRole)) +} + +// RolePriorityEntry is a single role in a RolePriority list. +type RolePriorityEntry struct { + // Types that are valid to be assigned to Role: + // *RolePriorityEntry_RegistryRole + // *RolePriorityEntry_NftRole + Role isRolePriorityEntry_Role `protobuf_oneof:"role"` +} + +func (m *RolePriorityEntry) Reset() { *m = RolePriorityEntry{} } +func (m *RolePriorityEntry) String() string { return proto.CompactTextString(m) } +func (*RolePriorityEntry) ProtoMessage() {} +func (m *RolePriorityEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (m *RolePriorityEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RolePriorityEntry.Marshal(b, m, deterministic) + } + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil +} +func (m *RolePriorityEntry) XXX_Merge(src proto.Message) { + xxx_messageInfo_RolePriorityEntry.Merge(m, src) +} +func (m *RolePriorityEntry) XXX_Size() int { return m.Size() } +func (m *RolePriorityEntry) XXX_DiscardUnknown() { + xxx_messageInfo_RolePriorityEntry.DiscardUnknown(m) +} + +var xxx_messageInfo_RolePriorityEntry proto.InternalMessageInfo + +func (m *RolePriorityEntry) GetRole() isRolePriorityEntry_Role { + if m != nil { + return m.Role + } + return nil +} + +func (m *RolePriorityEntry) GetRegistryRole() RegistryRole { + if x, ok := m.GetRole().(*RolePriorityEntry_RegistryRole); ok { + return x.RegistryRole + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RolePriorityEntry) GetNftRole() NftRole { + if x, ok := m.GetRole().(*RolePriorityEntry_NftRole); ok { + return x.NftRole + } + return NftRole_NFT_ROLE_UNSPECIFIED +} + +func (m *RolePriorityEntry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RolePriorityEntry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RolePriorityEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Role != nil { + n, err := m.Role.MarshalTo(dAtA[:i]) + if err != nil { + return 0, err + } + i -= n + } + return len(dAtA) - i, nil +} + +func (m *RolePriorityEntry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Role != nil { + n += m.Role.Size() + } + return n +} + +func (m *RolePriorityEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RolePriorityEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RolePriorityEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryRole", wireType) + } + var v RegistryRole + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Role = &RolePriorityEntry_RegistryRole{RegistryRole: v} + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NftRole", wireType) + } + var v NftRole + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= NftRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Role = &RolePriorityEntry_NftRole{NftRole: v} + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} + +// ----------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------- + +func encodeVarintAuthorization(dAtA []byte, offset int, v uint64) int { + offset -= sovAuthorization(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} + +func sovAuthorization(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} + +func skipAuthorization(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorization + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorization + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowAuthorization + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthAuthorization + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupAuthorization + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthAuthorization + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthAuthorization = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowAuthorization = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupAuthorization = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index fb07d4b58c..7c0d2ecf9c 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -14,6 +14,7 @@ var AllRequestMsgs = []sdk.Msg{ (*MsgRevokeRole)(nil), (*MsgUnregisterNFT)(nil), (*MsgRegistryBulkUpdate)(nil), + (*MsgSetRoles)(nil), } // ValidateBasic validates the MsgRegisterNFT message @@ -41,8 +42,14 @@ func (m MsgRegisterNFT) ValidateBasic() error { // ValidateBasic validates the MsgGrantRole message func (m MsgGrantRole) ValidateBasic() error { var errs []error - if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { - errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + if len(m.Signers) == 0 { + errs = append(errs, NewErrCodeInvalidField("signers", "at least one signer is required")) + } else { + for _, signer := range m.Signers { + if _, err := sdk.AccAddressFromBech32(signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signers", "%s", err)) + } + } } if err := m.Key.Validate(); err != nil { @@ -63,8 +70,14 @@ func (m MsgGrantRole) ValidateBasic() error { // ValidateBasic validates the MsgRevokeRole message func (m MsgRevokeRole) ValidateBasic() error { var errs []error - if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { - errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + if len(m.Signers) == 0 { + errs = append(errs, NewErrCodeInvalidField("signers", "at least one signer is required")) + } else { + for _, signer := range m.Signers { + if _, err := sdk.AccAddressFromBech32(signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signers", "%s", err)) + } + } } if err := m.Key.Validate(); err != nil { @@ -82,6 +95,41 @@ func (m MsgRevokeRole) ValidateBasic() error { return errors.Join(errs...) } +// ValidateBasic validates the MsgSetRoles message +func (m MsgSetRoles) ValidateBasic() error { + var errs []error + if len(m.Signers) == 0 { + errs = append(errs, NewErrCodeInvalidField("signers", "at least one signer is required")) + } else { + for _, signer := range m.Signers { + if _, err := sdk.AccAddressFromBech32(signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signers", "%s", err)) + } + } + } + + if err := m.Key.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("key", "%s", err)) + } + + if len(m.RoleUpdates) == 0 { + errs = append(errs, NewErrCodeInvalidField("role_updates", "at least one role update is required")) + } else { + for i, update := range m.RoleUpdates { + if update.Role == RegistryRole_REGISTRY_ROLE_UNSPECIFIED { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role cannot be unspecified", i)) + } + for _, addr := range update.Addresses { + if _, err := sdk.AccAddressFromBech32(addr); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: invalid address: %s", i, err)) + } + } + } + } + + return errors.Join(errs...) +} + // ValidateBasic validates the MsgUnregisterNFT message func (m MsgUnregisterNFT) ValidateBasic() error { var errs []error diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 91b9657671..2f18b05980 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -13,13 +13,16 @@ import ( func TestAllMsgsGetSigners(t *testing.T) { msgMakers := []testutil.MsgMaker{ func(signer string) sdk.Msg { return &MsgRegisterNFT{Signer: signer} }, - func(signer string) sdk.Msg { return &MsgGrantRole{Signer: signer} }, - func(signer string) sdk.Msg { return &MsgRevokeRole{Signer: signer} }, func(signer string) sdk.Msg { return &MsgUnregisterNFT{Signer: signer} }, func(signer string) sdk.Msg { return &MsgRegistryBulkUpdate{Signer: signer} }, } + msgMakersMulti := []testutil.MsgMakerMulti{ + func(signers []string) sdk.Msg { return &MsgGrantRole{Signers: signers} }, + func(signers []string) sdk.Msg { return &MsgRevokeRole{Signers: signers} }, + func(signers []string) sdk.Msg { return &MsgSetRoles{Signers: signers} }, + } - testutil.RunGetSignersTests(t, AllRequestMsgs, msgMakers, nil) + testutil.RunGetSignersTests(t, AllRequestMsgs, msgMakers, msgMakersMulti) } func TestMsgRegisterNFT_ValidateBasic(t *testing.T) { @@ -64,13 +67,13 @@ func TestMsgGrantRole_ValidateBasic(t *testing.T) { msg MsgGrantRole exp string }{ - {name: "valid", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}}, - {name: "empty signer", msg: MsgGrantRole{Signer: "", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signer: empty address"}, - {name: "bad signer", msg: MsgGrantRole{Signer: "bad", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signer: decoding bech32"}, - {name: "nil key", msg: MsgGrantRole{Signer: validAddr, Key: nil, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, - {name: "invalid role", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, - {name: "no addresses", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, - {name: "bad address", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + {name: "valid", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}}, + {name: "no signers", msg: MsgGrantRole{Signers: []string{}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signers: at least one signer is required"}, + {name: "bad signer", msg: MsgGrantRole{Signers: []string{"bad"}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signers: decoding bech32"}, + {name: "nil key", msg: MsgGrantRole{Signers: []string{validAddr}, Key: nil, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, + {name: "invalid role", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, + {name: "no addresses", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, + {name: "bad address", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, } for _, tc := range tests { @@ -95,13 +98,46 @@ func TestMsgRevokeRole_ValidateBasic(t *testing.T) { msg MsgRevokeRole exp string }{ - {name: "valid", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}}, - {name: "empty signer", msg: MsgRevokeRole{Signer: "", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signer: empty address"}, - {name: "bad signer", msg: MsgRevokeRole{Signer: "bad", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signer: decoding bech32"}, - {name: "nil key", msg: MsgRevokeRole{Signer: validAddr, Key: nil, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, - {name: "invalid role", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, - {name: "no addresses", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, - {name: "bad address", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + {name: "valid", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}}, + {name: "no signers", msg: MsgRevokeRole{Signers: []string{}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signers: at least one signer is required"}, + {name: "bad signer", msg: MsgRevokeRole{Signers: []string{"bad"}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signers: decoding bech32"}, + {name: "nil key", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: nil, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, + {name: "invalid role", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, + {name: "no addresses", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, + {name: "bad address", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.exp == "" { + assertions.RequireErrorContents(t, err, nil) + } else { + assertions.RequireErrorContents(t, err, []string{tc.exp}) + } + }) + } +} + +func TestMsgSetRoles_ValidateBasic(t *testing.T) { + validAddr := sdk.AccAddress("setroles_signer________").String() + otherAddr := sdk.AccAddress("other_addr3___________").String() + validKey := &RegistryKey{AssetClassId: "aclass", NftId: "nft1"} + validUpdate := RoleUpdate{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr}} + + tests := []struct { + name string + msg MsgSetRoles + exp string + }{ + {name: "valid", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}}, + {name: "valid clear role", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER}}}}, + {name: "no signers", msg: MsgSetRoles{Signers: []string{}, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signers: at least one signer is required"}, + {name: "bad signer", msg: MsgSetRoles{Signers: []string{"bad"}, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signers: decoding bech32"}, + {name: "nil key", msg: MsgSetRoles{Signers: []string{validAddr}, Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: registry key cannot be nil"}, + {name: "no role_updates", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, + {name: "unspecified role", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, + {name: "bad address in update", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } for _, tc := range tests { diff --git a/x/registry/types/registry.pb.go b/x/registry/types/registry.pb.go index 7dd90888d4..66f429cd12 100644 --- a/x/registry/types/registry.pb.go +++ b/x/registry/types/registry.pb.go @@ -50,26 +50,45 @@ const ( // REGISTRY_ROLE_ORIGINATOR indicates the address has originator privileges. // Originators are responsible for creating and originating the underlying assets. RegistryRole_REGISTRY_ROLE_ORIGINATOR RegistryRole = 6 + // REGISTRY_ROLE_LIEN_OWNER indicates the entity on whose behalf the registry acts as a nominal lien holder. + RegistryRole_REGISTRY_ROLE_LIEN_OWNER RegistryRole = 7 + // REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN indicates an organization with a security interest in the lien. + // This party can foreclose into the Lien Owner role or direct a transfer in case of default. + RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN RegistryRole = 8 + // REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE indicates an organization with a financial interest in the eNote. + // This party can foreclose into the Controller role or direct a transfer in case of default. + RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE RegistryRole = 9 + // REGISTRY_ROLE_PLEDGEE indicates a lender or creditor that holds a security interest in pledged loans + // as collateral securing an obligation of the Pledgor. + RegistryRole_REGISTRY_ROLE_PLEDGEE RegistryRole = 10 ) var RegistryRole_name = map[int32]string{ - 0: "REGISTRY_ROLE_UNSPECIFIED", - 1: "REGISTRY_ROLE_SERVICER", - 2: "REGISTRY_ROLE_SUBSERVICER", - 3: "REGISTRY_ROLE_CONTROLLER", - 4: "REGISTRY_ROLE_CUSTODIAN", - 5: "REGISTRY_ROLE_BORROWER", - 6: "REGISTRY_ROLE_ORIGINATOR", + 0: "REGISTRY_ROLE_UNSPECIFIED", + 1: "REGISTRY_ROLE_SERVICER", + 2: "REGISTRY_ROLE_SUBSERVICER", + 3: "REGISTRY_ROLE_CONTROLLER", + 4: "REGISTRY_ROLE_CUSTODIAN", + 5: "REGISTRY_ROLE_BORROWER", + 6: "REGISTRY_ROLE_ORIGINATOR", + 7: "REGISTRY_ROLE_LIEN_OWNER", + 8: "REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN", + 9: "REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE", + 10: "REGISTRY_ROLE_PLEDGEE", } var RegistryRole_value = map[string]int32{ - "REGISTRY_ROLE_UNSPECIFIED": 0, - "REGISTRY_ROLE_SERVICER": 1, - "REGISTRY_ROLE_SUBSERVICER": 2, - "REGISTRY_ROLE_CONTROLLER": 3, - "REGISTRY_ROLE_CUSTODIAN": 4, - "REGISTRY_ROLE_BORROWER": 5, - "REGISTRY_ROLE_ORIGINATOR": 6, + "REGISTRY_ROLE_UNSPECIFIED": 0, + "REGISTRY_ROLE_SERVICER": 1, + "REGISTRY_ROLE_SUBSERVICER": 2, + "REGISTRY_ROLE_CONTROLLER": 3, + "REGISTRY_ROLE_CUSTODIAN": 4, + "REGISTRY_ROLE_BORROWER": 5, + "REGISTRY_ROLE_ORIGINATOR": 6, + "REGISTRY_ROLE_LIEN_OWNER": 7, + "REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN": 8, + "REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE": 9, + "REGISTRY_ROLE_PLEDGEE": 10, } func (x RegistryRole) String() string { @@ -267,37 +286,41 @@ func init() { } var fileDescriptor_2fa7a0b4d34d0208 = []byte{ - // 476 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcd, 0x6e, 0xd3, 0x40, - 0x14, 0x85, 0xed, 0xfc, 0x49, 0x99, 0x94, 0xca, 0x1a, 0x95, 0x92, 0x04, 0x70, 0xab, 0x50, 0xa4, - 0x0a, 0xa9, 0xb6, 0x1a, 0x7e, 0x84, 0x58, 0x20, 0xc5, 0xa9, 0xa9, 0x46, 0x44, 0x71, 0x74, 0x9d, - 0x80, 0x60, 0x63, 0xa5, 0xf1, 0xd4, 0x58, 0x6d, 0x3d, 0x91, 0x67, 0x88, 0xf0, 0x86, 0x25, 0x6b, - 0x96, 0x2c, 0x79, 0x08, 0x1e, 0xa2, 0xcb, 0x8a, 0x15, 0x1b, 0x10, 0x4a, 0x5e, 0x04, 0xd9, 0x0e, - 0x71, 0x4a, 0xa8, 0xba, 0x9b, 0xb9, 0xdf, 0x39, 0x67, 0x8e, 0x46, 0x17, 0xdd, 0x1f, 0x87, 0x6c, - 0x42, 0x83, 0x61, 0x30, 0xa2, 0x7a, 0x48, 0x3d, 0x9f, 0x8b, 0x30, 0xd2, 0x27, 0xfb, 0x8b, 0xb3, - 0x36, 0x0e, 0x99, 0x60, 0x78, 0x33, 0x93, 0x69, 0x0b, 0x34, 0xd9, 0xaf, 0x6f, 0x78, 0xcc, 0x63, - 0x89, 0x44, 0x8f, 0x4f, 0xa9, 0xba, 0x5e, 0x1b, 0x31, 0x7e, 0xc6, 0xb8, 0x93, 0x82, 0xf4, 0x92, - 0xa2, 0x46, 0x0f, 0x55, 0x60, 0xee, 0x7f, 0x49, 0x23, 0x7c, 0x13, 0x95, 0x82, 0x63, 0xe1, 0xf8, - 0x6e, 0x55, 0xde, 0x96, 0x77, 0xcb, 0x50, 0x0c, 0x8e, 0x05, 0x71, 0xf1, 0x0e, 0x5a, 0x1f, 0x72, - 0x4e, 0x85, 0x33, 0x3a, 0x1d, 0x72, 0x1e, 0xe3, 0x5c, 0x82, 0xd7, 0x92, 0x69, 0x3b, 0x1e, 0x12, - 0xf7, 0x59, 0xe1, 0xcb, 0xd7, 0x2d, 0xa9, 0xf1, 0x49, 0x46, 0x37, 0xfe, 0x46, 0x9a, 0x81, 0x08, - 0x23, 0xfc, 0x18, 0xe5, 0x4f, 0x68, 0x94, 0x24, 0x56, 0x9a, 0xf7, 0xb4, 0xff, 0x57, 0xd7, 0x96, - 0x6a, 0x40, 0xac, 0xc7, 0xcf, 0x51, 0x31, 0x64, 0xa7, 0x94, 0x57, 0x73, 0xdb, 0xf9, 0xdd, 0x4a, - 0xb3, 0x71, 0xa5, 0x31, 0x16, 0x25, 0x2f, 0x19, 0x85, 0xf3, 0x5f, 0x5b, 0x12, 0xa4, 0xb6, 0xc6, - 0x47, 0x84, 0x32, 0x84, 0x9f, 0xa2, 0x42, 0x3c, 0x4e, 0x5a, 0xac, 0x37, 0x77, 0xae, 0x6b, 0x11, - 0x3b, 0x21, 0x71, 0xe0, 0x27, 0xa8, 0x3c, 0x74, 0xdd, 0x90, 0x72, 0x3e, 0xef, 0x52, 0x36, 0xaa, - 0xdf, 0xbf, 0xed, 0x6d, 0xcc, 0xff, 0xb1, 0x95, 0x32, 0x5b, 0x84, 0x7e, 0xe0, 0x41, 0x26, 0x7d, - 0xf0, 0x53, 0x46, 0x6b, 0xcb, 0x71, 0xf8, 0x2e, 0xaa, 0x81, 0x79, 0x48, 0xec, 0x3e, 0xbc, 0x71, - 0xc0, 0xea, 0x98, 0xce, 0xa0, 0x6b, 0xf7, 0xcc, 0x36, 0x79, 0x41, 0xcc, 0x03, 0x45, 0xc2, 0x75, - 0xb4, 0x79, 0x19, 0xdb, 0x26, 0xbc, 0x22, 0x6d, 0x13, 0x14, 0x79, 0xd5, 0x6a, 0x0f, 0x8c, 0x05, - 0xce, 0xe1, 0x3b, 0xa8, 0x7a, 0x19, 0xb7, 0xad, 0x6e, 0x1f, 0xac, 0x4e, 0xc7, 0x04, 0x25, 0x8f, - 0x6f, 0xa3, 0x5b, 0xff, 0xd0, 0x81, 0xdd, 0xb7, 0x0e, 0x48, 0xab, 0xab, 0x14, 0x56, 0x5f, 0x35, - 0x2c, 0x00, 0xeb, 0xb5, 0x09, 0x4a, 0x71, 0x35, 0xd6, 0x02, 0x72, 0x48, 0xba, 0xad, 0xbe, 0x05, - 0x4a, 0xc9, 0x38, 0x39, 0x9f, 0xaa, 0xf2, 0xc5, 0x54, 0x95, 0x7f, 0x4f, 0x55, 0xf9, 0xf3, 0x4c, - 0x95, 0x2e, 0x66, 0xaa, 0xf4, 0x63, 0xa6, 0x4a, 0xa8, 0xe6, 0xb3, 0x2b, 0xfe, 0xb7, 0x27, 0xbf, - 0x7d, 0xe4, 0xf9, 0xe2, 0xdd, 0xfb, 0x23, 0x6d, 0xc4, 0xce, 0xf4, 0x4c, 0xb4, 0xe7, 0xb3, 0xa5, - 0x9b, 0xfe, 0x21, 0x5b, 0x7e, 0x11, 0x8d, 0x29, 0x3f, 0x2a, 0x25, 0xeb, 0xfa, 0xf0, 0x4f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x4f, 0x36, 0x83, 0x50, 0x20, 0x03, 0x00, 0x00, + // 532 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xcb, 0x6e, 0xd3, 0x40, + 0x14, 0x86, 0xed, 0xdc, 0x20, 0x27, 0xa5, 0xb2, 0x46, 0x6d, 0x49, 0x02, 0xb8, 0x55, 0x48, 0xa5, + 0x80, 0xd4, 0x44, 0x0d, 0x17, 0x21, 0x16, 0x48, 0xb9, 0x4c, 0x23, 0x8b, 0xc8, 0x8e, 0x8e, 0x1d, + 0xaa, 0xb2, 0xb1, 0xd2, 0xc4, 0x0d, 0x56, 0x5b, 0x4f, 0xe4, 0x31, 0x11, 0xd9, 0xb0, 0x64, 0xcd, + 0x92, 0x25, 0x0f, 0xc1, 0x43, 0x74, 0x59, 0xb1, 0x62, 0x85, 0x50, 0xb2, 0xe4, 0x25, 0x90, 0xed, + 0x90, 0x4b, 0xd3, 0xaa, 0xbb, 0x99, 0xf3, 0x7d, 0xff, 0x99, 0x63, 0x8f, 0x06, 0x76, 0x07, 0x2e, + 0x1b, 0x5a, 0x4e, 0xc7, 0xe9, 0x5a, 0x25, 0xd7, 0xea, 0xdb, 0xdc, 0x73, 0x47, 0xa5, 0xe1, 0xfe, + 0x6c, 0x5d, 0x1c, 0xb8, 0xcc, 0x63, 0x64, 0x6b, 0xae, 0x15, 0x67, 0x68, 0xb8, 0x9f, 0xdd, 0xe8, + 0xb3, 0x3e, 0x0b, 0x94, 0x92, 0xbf, 0x0a, 0xed, 0x6c, 0xa6, 0xcb, 0xf8, 0x39, 0xe3, 0x66, 0x08, + 0xc2, 0x4d, 0x88, 0x72, 0x2d, 0x48, 0xe1, 0x34, 0xff, 0xd6, 0x1a, 0x91, 0x4d, 0x48, 0x38, 0x27, + 0x9e, 0x69, 0xf7, 0xd2, 0xe2, 0x8e, 0x58, 0x48, 0x62, 0xdc, 0x39, 0xf1, 0x94, 0x1e, 0xc9, 0xc3, + 0x7a, 0x87, 0x73, 0xcb, 0x33, 0xbb, 0x67, 0x1d, 0xce, 0x7d, 0x1c, 0x09, 0xf0, 0x5a, 0x50, 0xad, + 0xf9, 0x45, 0xa5, 0xf7, 0x3a, 0xf6, 0xed, 0xfb, 0xb6, 0x90, 0xfb, 0x22, 0xc2, 0xbd, 0xff, 0x2d, + 0xa9, 0xe3, 0xb9, 0x23, 0xf2, 0x02, 0xa2, 0xa7, 0xd6, 0x28, 0xe8, 0x98, 0x2a, 0x3f, 0x2e, 0x5e, + 0x3f, 0x7a, 0x71, 0x61, 0x0c, 0xf4, 0x7d, 0xf2, 0x06, 0xe2, 0x2e, 0x3b, 0xb3, 0x78, 0x3a, 0xb2, + 0x13, 0x2d, 0xa4, 0xca, 0xb9, 0x1b, 0x83, 0xbe, 0x14, 0x9c, 0x54, 0x8d, 0x5d, 0xfc, 0xde, 0x16, + 0x30, 0x8c, 0xe5, 0x3e, 0x03, 0xcc, 0x11, 0x79, 0x05, 0x31, 0xbf, 0x1c, 0x4c, 0xb1, 0x5e, 0xce, + 0xdf, 0x36, 0x85, 0x9f, 0xc4, 0x20, 0x41, 0x5e, 0x42, 0xb2, 0xd3, 0xeb, 0xb9, 0x16, 0xe7, 0xd3, + 0x59, 0x92, 0xd5, 0xf4, 0xcf, 0x1f, 0x7b, 0x1b, 0xd3, 0xff, 0x58, 0x09, 0x99, 0xee, 0xb9, 0xb6, + 0xd3, 0xc7, 0xb9, 0xfa, 0xf4, 0x6f, 0x04, 0xd6, 0x16, 0xdb, 0x91, 0x47, 0x90, 0x41, 0xda, 0x50, + 0x74, 0x03, 0x8f, 0x4c, 0xd4, 0x9a, 0xd4, 0x6c, 0xab, 0x7a, 0x8b, 0xd6, 0x94, 0x03, 0x85, 0xd6, + 0x25, 0x81, 0x64, 0x61, 0x6b, 0x19, 0xeb, 0x14, 0xdf, 0x29, 0x35, 0x8a, 0x92, 0xb8, 0x1a, 0xd5, + 0xdb, 0xd5, 0x19, 0x8e, 0x90, 0x87, 0x90, 0x5e, 0xc6, 0x35, 0x4d, 0x35, 0x50, 0x6b, 0x36, 0x29, + 0x4a, 0x51, 0xf2, 0x00, 0xee, 0x5f, 0xa1, 0x6d, 0xdd, 0xd0, 0xea, 0x4a, 0x45, 0x95, 0x62, 0xab, + 0xa7, 0x56, 0x35, 0x44, 0xed, 0x90, 0xa2, 0x14, 0x5f, 0x6d, 0xab, 0xa1, 0xd2, 0x50, 0xd4, 0x8a, + 0xa1, 0xa1, 0x94, 0x58, 0xa5, 0x4d, 0x85, 0xaa, 0xa6, 0x76, 0xa8, 0x52, 0x94, 0xee, 0x90, 0x02, + 0xe4, 0xaf, 0x7e, 0x4d, 0xad, 0x8d, 0xb4, 0x6e, 0xb6, 0x2a, 0x68, 0x1c, 0x99, 0x07, 0x1a, 0x06, + 0xbe, 0x74, 0x97, 0x3c, 0x81, 0xdd, 0xdb, 0x4c, 0xaa, 0x6a, 0x06, 0x95, 0x92, 0x24, 0x03, 0x9b, + 0xcb, 0x6a, 0xab, 0x49, 0xeb, 0x0d, 0x4a, 0x25, 0xa8, 0x9e, 0x5e, 0x8c, 0x65, 0xf1, 0x72, 0x2c, + 0x8b, 0x7f, 0xc6, 0xb2, 0xf8, 0x75, 0x22, 0x0b, 0x97, 0x13, 0x59, 0xf8, 0x35, 0x91, 0x05, 0xc8, + 0xd8, 0xec, 0x86, 0xdb, 0x6e, 0x89, 0xef, 0x9f, 0xf7, 0x6d, 0xef, 0xc3, 0xc7, 0xe3, 0x62, 0x97, + 0x9d, 0x97, 0xe6, 0xd2, 0x9e, 0xcd, 0x16, 0x76, 0xa5, 0x4f, 0xf3, 0xa7, 0xe8, 0x8d, 0x06, 0x16, + 0x3f, 0x4e, 0x04, 0x8f, 0xe7, 0xd9, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x97, 0xb2, 0x95, 0xbf, + 0xae, 0x03, 0x00, 0x00, } func (m *RegistryKey) Marshal() (dAtA []byte, err error) { diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 4074e10e09..3e3022ade2 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -138,10 +138,12 @@ var xxx_messageInfo_MsgRegisterNFTResponse proto.InternalMessageInfo // MsgGrantRole represents a message to grant a role to one or more addresses. // This message adds the specified addresses to an existing role for the given registry key. +// Multiple signers enable multi-party authorization workflows. type MsgGrantRole struct { - // signer is the address that is authorized to grant the role. - // This address must have the appropriate permissions to modify role assignments. - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // signers are the addresses authorizing the role grant. + // When a RegistryClass authorization policy is defined for the target role, all required + // role holders must be included here. Without a policy, a single NFT owner address is required. + Signers []string `protobuf:"bytes,1,rep,name=signers,proto3" json:"signers,omitempty"` // key is the registry key to grant the role to. // This identifies the specific registry entry to modify. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` @@ -186,11 +188,11 @@ func (m *MsgGrantRole) XXX_DiscardUnknown() { var xxx_messageInfo_MsgGrantRole proto.InternalMessageInfo -func (m *MsgGrantRole) GetSigner() string { +func (m *MsgGrantRole) GetSigners() []string { if m != nil { - return m.Signer + return m.Signers } - return "" + return nil } func (m *MsgGrantRole) GetKey() *RegistryKey { @@ -254,10 +256,12 @@ var xxx_messageInfo_MsgGrantRoleResponse proto.InternalMessageInfo // MsgRevokeRole represents a message to revoke a role from one or more addresses. // This message removes the specified addresses from an existing role for the given registry key. +// Multiple signers enable multi-party authorization workflows. type MsgRevokeRole struct { - // signer is the address that is authorized to revoke the role. - // This address must have the appropriate permissions to modify role assignments. - Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // signers are the addresses authorizing the role revocation. + // When a RegistryClass authorization policy is defined for the target role, all required + // role holders must be included here. Without a policy, a single NFT owner address is required. + Signers []string `protobuf:"bytes,1,rep,name=signers,proto3" json:"signers,omitempty"` // key is the registry key to revoke the role from. // This identifies the specific registry entry to modify. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` @@ -302,11 +306,11 @@ func (m *MsgRevokeRole) XXX_DiscardUnknown() { var xxx_messageInfo_MsgRevokeRole proto.InternalMessageInfo -func (m *MsgRevokeRole) GetSigner() string { +func (m *MsgRevokeRole) GetSigners() []string { if m != nil { - return m.Signer + return m.Signers } - return "" + return nil } func (m *MsgRevokeRole) GetKey() *RegistryKey { @@ -560,6 +564,168 @@ func (m *MsgRegistryBulkUpdateResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgRegistryBulkUpdateResponse proto.InternalMessageInfo +// MsgSetRoles atomically sets the desired state for one or more roles on a registry entry. +// For each role in role_updates, the keeper computes the diff between current and desired +// addresses and applies grants and revokes to reach the desired state. +// All updates succeed or all fail (atomic). +type MsgSetRoles struct { + // signers are the addresses authorizing the role updates. + // All required role holders per the RegistryClass authorization policy must be present. + Signers []string `protobuf:"bytes,1,rep,name=signers,proto3" json:"signers,omitempty"` + // key identifies the registry entry to update. + Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // role_updates contains the desired state for each role to update. + // At least one entry is required. Roles not listed here are unchanged. + RoleUpdates []RoleUpdate `protobuf:"bytes,3,rep,name=role_updates,json=roleUpdates,proto3" json:"role_updates"` +} + +func (m *MsgSetRoles) Reset() { *m = MsgSetRoles{} } +func (m *MsgSetRoles) String() string { return proto.CompactTextString(m) } +func (*MsgSetRoles) ProtoMessage() {} +func (*MsgSetRoles) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{10} +} +func (m *MsgSetRoles) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetRoles) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetRoles.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetRoles) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetRoles.Merge(m, src) +} +func (m *MsgSetRoles) XXX_Size() int { + return m.Size() +} +func (m *MsgSetRoles) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetRoles.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetRoles proto.InternalMessageInfo + +func (m *MsgSetRoles) GetSigners() []string { + if m != nil { + return m.Signers + } + return nil +} + +func (m *MsgSetRoles) GetKey() *RegistryKey { + if m != nil { + return m.Key + } + return nil +} + +func (m *MsgSetRoles) GetRoleUpdates() []RoleUpdate { + if m != nil { + return m.RoleUpdates + } + return nil +} + +// RoleUpdate specifies the desired set of addresses for a single role. +type RoleUpdate struct { + // role is the role to update. + Role RegistryRole `protobuf:"varint,1,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` + // addresses is the desired set of addresses for this role. + // An empty list means the role should be cleared entirely. + Addresses []string `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"` +} + +func (m *RoleUpdate) Reset() { *m = RoleUpdate{} } +func (m *RoleUpdate) String() string { return proto.CompactTextString(m) } +func (*RoleUpdate) ProtoMessage() {} +func (*RoleUpdate) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{11} +} +func (m *RoleUpdate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RoleUpdate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RoleUpdate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RoleUpdate) XXX_Merge(src proto.Message) { + xxx_messageInfo_RoleUpdate.Merge(m, src) +} +func (m *RoleUpdate) XXX_Size() int { + return m.Size() +} +func (m *RoleUpdate) XXX_DiscardUnknown() { + xxx_messageInfo_RoleUpdate.DiscardUnknown(m) +} + +var xxx_messageInfo_RoleUpdate proto.InternalMessageInfo + +func (m *RoleUpdate) GetRole() RegistryRole { + if m != nil { + return m.Role + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RoleUpdate) GetAddresses() []string { + if m != nil { + return m.Addresses + } + return nil +} + +// MsgSetRolesResponse defines the response for SetRoles. +type MsgSetRolesResponse struct { +} + +func (m *MsgSetRolesResponse) Reset() { *m = MsgSetRolesResponse{} } +func (m *MsgSetRolesResponse) String() string { return proto.CompactTextString(m) } +func (*MsgSetRolesResponse) ProtoMessage() {} +func (*MsgSetRolesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{12} +} +func (m *MsgSetRolesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgSetRolesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgSetRolesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgSetRolesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgSetRolesResponse.Merge(m, src) +} +func (m *MsgSetRolesResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgSetRolesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgSetRolesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgSetRolesResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgRegisterNFT)(nil), "provenance.registry.v1.MsgRegisterNFT") proto.RegisterType((*MsgRegisterNFTResponse)(nil), "provenance.registry.v1.MsgRegisterNFTResponse") @@ -571,49 +737,59 @@ func init() { proto.RegisterType((*MsgUnregisterNFTResponse)(nil), "provenance.registry.v1.MsgUnregisterNFTResponse") proto.RegisterType((*MsgRegistryBulkUpdate)(nil), "provenance.registry.v1.MsgRegistryBulkUpdate") proto.RegisterType((*MsgRegistryBulkUpdateResponse)(nil), "provenance.registry.v1.MsgRegistryBulkUpdateResponse") + proto.RegisterType((*MsgSetRoles)(nil), "provenance.registry.v1.MsgSetRoles") + proto.RegisterType((*RoleUpdate)(nil), "provenance.registry.v1.RoleUpdate") + proto.RegisterType((*MsgSetRolesResponse)(nil), "provenance.registry.v1.MsgSetRolesResponse") } func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 587 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x95, 0x3d, 0x6f, 0xd3, 0x40, - 0x18, 0xc7, 0x73, 0x75, 0x5a, 0xd4, 0x27, 0xb4, 0x42, 0x56, 0x48, 0x5d, 0x4b, 0x38, 0x51, 0x68, - 0x50, 0x54, 0x11, 0xbb, 0x0d, 0x14, 0x21, 0x06, 0x24, 0x22, 0x15, 0x06, 0x14, 0x84, 0x0c, 0x5d, - 0x58, 0xaa, 0xbc, 0x9c, 0x0e, 0x2b, 0x8d, 0x2f, 0xba, 0xbb, 0x46, 0x35, 0x13, 0x62, 0x62, 0xe4, - 0x0b, 0x30, 0xf0, 0x0d, 0x3a, 0xf0, 0x19, 0x50, 0xc7, 0x8a, 0x89, 0x09, 0xa1, 0x64, 0xe8, 0x37, - 0x80, 0x15, 0xf9, 0xdd, 0x46, 0x79, 0x83, 0x01, 0x06, 0x36, 0x5f, 0xee, 0xf7, 0xdc, 0xff, 0xff, - 0x7f, 0xe2, 0xc7, 0x07, 0xc5, 0x01, 0xa3, 0x43, 0x6c, 0xb7, 0xec, 0x0e, 0x36, 0x18, 0x26, 0x16, - 0x17, 0xcc, 0x31, 0x86, 0xbb, 0x86, 0x38, 0xd1, 0x07, 0x8c, 0x0a, 0x2a, 0x17, 0x62, 0x40, 0x0f, - 0x01, 0x7d, 0xb8, 0xab, 0xe6, 0x09, 0x25, 0xd4, 0x43, 0x0c, 0xf7, 0xc9, 0xa7, 0xd5, 0xcd, 0x0e, - 0xe5, 0x7d, 0xca, 0x0f, 0xfd, 0x0d, 0x7f, 0x11, 0x6c, 0x6d, 0xf8, 0x2b, 0xa3, 0xcf, 0x89, 0x2b, - 0xd0, 0xe7, 0x24, 0xd8, 0xa8, 0x4c, 0xb1, 0x10, 0xa9, 0x79, 0x58, 0xf9, 0x13, 0x82, 0xf5, 0x26, - 0x27, 0xa6, 0xf7, 0x2b, 0x66, 0x4f, 0x1e, 0x3e, 0x97, 0x77, 0x60, 0x85, 0x5b, 0xc4, 0xc6, 0x4c, - 0x41, 0x25, 0x54, 0x5d, 0x6d, 0x28, 0x9f, 0x3f, 0xd6, 0xf2, 0x81, 0xe8, 0x83, 0x6e, 0x97, 0x61, - 0xce, 0x9f, 0x09, 0x66, 0xd9, 0xc4, 0x0c, 0x38, 0x79, 0x0f, 0xa4, 0x1e, 0x76, 0x94, 0xa5, 0x12, - 0xaa, 0xe6, 0xea, 0xd7, 0xf5, 0xc9, 0xd9, 0x74, 0x33, 0x78, 0x7e, 0x8c, 0x1d, 0xd3, 0xe5, 0xe5, - 0xfb, 0xb0, 0xcc, 0xe8, 0x11, 0xe6, 0x8a, 0x54, 0x92, 0xaa, 0xb9, 0x7a, 0x79, 0x6a, 0xa1, 0x0b, - 0xed, 0xdb, 0x82, 0x39, 0x8d, 0xec, 0xd9, 0xd7, 0x62, 0xc6, 0xf4, 0xcb, 0xee, 0xe5, 0xde, 0x5c, - 0x9c, 0x6e, 0x07, 0x1e, 0xca, 0x0a, 0x14, 0xd2, 0x39, 0x4c, 0xcc, 0x07, 0xd4, 0xe6, 0xb8, 0xfc, - 0x1d, 0xc1, 0xe5, 0x26, 0x27, 0x8f, 0x58, 0xcb, 0x16, 0xee, 0x51, 0x7f, 0x2f, 0xe0, 0x5d, 0xc8, - 0xba, 0x4e, 0x15, 0xa9, 0x84, 0xaa, 0xeb, 0xf5, 0xad, 0x79, 0x75, 0xae, 0x39, 0xd3, 0xab, 0x90, - 0xef, 0xc0, 0x6a, 0xcb, 0x77, 0x82, 0xb9, 0x92, 0x2d, 0x49, 0x33, 0x5d, 0xc6, 0x68, 0xba, 0x25, - 0x05, 0xc8, 0x27, 0x73, 0x47, 0x0d, 0xf9, 0x81, 0x60, 0xcd, 0xeb, 0xd5, 0x90, 0xf6, 0xf0, 0x7f, - 0xd5, 0x91, 0x0d, 0xb8, 0x9a, 0x0a, 0x1e, 0xb5, 0xe4, 0x2d, 0x82, 0x2b, 0x4d, 0x4e, 0x0e, 0x6c, - 0xf6, 0x0f, 0x06, 0x21, 0xed, 0x51, 0x05, 0xe5, 0x57, 0x27, 0x91, 0xcd, 0xf7, 0x28, 0x08, 0xe0, - 0x1f, 0xd0, 0x38, 0x3e, 0xea, 0x1d, 0x0c, 0xba, 0x2d, 0xf1, 0x27, 0xff, 0xe0, 0x3e, 0x5c, 0xc2, - 0xb6, 0x60, 0x16, 0xe6, 0xca, 0x92, 0x37, 0x7f, 0x95, 0x79, 0x7e, 0x93, 0x23, 0x18, 0xd6, 0xa6, - 0xbd, 0x17, 0xe1, 0xda, 0x44, 0x7b, 0x61, 0x80, 0xfa, 0x87, 0x2c, 0x48, 0x4d, 0x4e, 0x64, 0x0c, - 0xb9, 0xe4, 0x27, 0xe7, 0xc6, 0x34, 0xe9, 0xf4, 0x48, 0xab, 0xfa, 0x62, 0x5c, 0x28, 0x27, 0x1f, - 0xc2, 0x6a, 0x3c, 0xf6, 0x5b, 0x33, 0x8a, 0x23, 0x4a, 0xbd, 0xb9, 0x08, 0x15, 0x09, 0xb4, 0x01, - 0x12, 0x63, 0x54, 0x99, 0x69, 0x2f, 0xc4, 0xd4, 0xda, 0x42, 0x58, 0xa4, 0xd1, 0x83, 0xb5, 0xf4, - 0x7b, 0x59, 0x9d, 0x51, 0x9f, 0x22, 0xd5, 0x9d, 0x45, 0xc9, 0x48, 0xec, 0x15, 0xc8, 0x13, 0xde, - 0xae, 0xda, 0xdc, 0xbe, 0x27, 0x71, 0x75, 0xef, 0xb7, 0xf0, 0x50, 0x5b, 0x5d, 0x7e, 0x7d, 0x71, - 0xba, 0x8d, 0x1a, 0xbd, 0xb3, 0x91, 0x86, 0xce, 0x47, 0x1a, 0xfa, 0x36, 0xd2, 0xd0, 0xbb, 0xb1, - 0x96, 0x39, 0x1f, 0x6b, 0x99, 0x2f, 0x63, 0x2d, 0x03, 0x9b, 0x16, 0x9d, 0x72, 0xf2, 0x53, 0xf4, - 0xe2, 0x36, 0xb1, 0xc4, 0xcb, 0xe3, 0xb6, 0xde, 0xa1, 0x7d, 0x23, 0x86, 0x6a, 0x16, 0x4d, 0xac, - 0x8c, 0x93, 0xf8, 0x2e, 0x14, 0xce, 0x00, 0xf3, 0xf6, 0x8a, 0x77, 0x0d, 0xde, 0xfa, 0x19, 0x00, - 0x00, 0xff, 0xff, 0x14, 0x84, 0xbd, 0x73, 0xb2, 0x07, 0x00, 0x00, + // 694 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x96, 0xcf, 0x4f, 0x13, 0x41, + 0x14, 0xc7, 0x3b, 0xb4, 0x80, 0xbc, 0x02, 0x31, 0x2b, 0x3f, 0x96, 0x4d, 0x2c, 0x4d, 0x01, 0xd3, + 0xa0, 0xdd, 0x85, 0x2a, 0xc6, 0x70, 0x30, 0xb1, 0x09, 0x7a, 0x20, 0x35, 0x66, 0x91, 0x8b, 0x31, + 0x21, 0x05, 0x26, 0xc3, 0xa6, 0x74, 0xa7, 0x99, 0x99, 0x36, 0x94, 0x83, 0x31, 0x9e, 0x3c, 0xfa, + 0x0f, 0xf8, 0x3f, 0x70, 0xf0, 0x6f, 0x30, 0x1c, 0x89, 0x5e, 0x3c, 0x19, 0x03, 0x07, 0xfe, 0x06, + 0xbd, 0x68, 0xf6, 0xd7, 0xec, 0x2e, 0xa1, 0xed, 0x8a, 0x89, 0x1e, 0xbc, 0xed, 0xec, 0x7c, 0xde, + 0x7b, 0xdf, 0xf7, 0xf2, 0xde, 0xcb, 0xc0, 0x6c, 0x93, 0xd1, 0x36, 0xb6, 0x6b, 0xf6, 0x0e, 0x36, + 0x18, 0x26, 0x16, 0x17, 0xac, 0x63, 0xb4, 0x97, 0x0d, 0x71, 0xa0, 0x37, 0x19, 0x15, 0x54, 0x99, + 0x0a, 0x01, 0x3d, 0x00, 0xf4, 0xf6, 0xb2, 0x36, 0x41, 0x28, 0xa1, 0x2e, 0x62, 0x38, 0x5f, 0x1e, + 0xad, 0xcd, 0xec, 0x50, 0xde, 0xa0, 0x7c, 0xcb, 0xbb, 0xf0, 0x0e, 0xfe, 0xd5, 0xb4, 0x77, 0x32, + 0x1a, 0x9c, 0x38, 0x01, 0x1a, 0x9c, 0xf8, 0x17, 0x0b, 0x5d, 0x24, 0xc8, 0x68, 0x1e, 0xb6, 0xd8, + 0x05, 0xab, 0xb5, 0xc4, 0x1e, 0x65, 0xd6, 0x61, 0x4d, 0x58, 0xd4, 0xf6, 0xd8, 0xc2, 0x47, 0x04, + 0xe3, 0x55, 0x4e, 0x4c, 0x17, 0xc3, 0xec, 0xe9, 0xe3, 0xe7, 0xca, 0x12, 0x0c, 0x71, 0x8b, 0xd8, + 0x98, 0xa9, 0x28, 0x8f, 0x8a, 0x23, 0x15, 0xf5, 0xd3, 0x87, 0xd2, 0x84, 0x2f, 0xf0, 0xd1, 0xee, + 0x2e, 0xc3, 0x9c, 0x6f, 0x08, 0x66, 0xd9, 0xc4, 0xf4, 0x39, 0x65, 0x05, 0xd2, 0x75, 0xdc, 0x51, + 0x07, 0xf2, 0xa8, 0x98, 0x2d, 0xcf, 0xe9, 0x97, 0xd7, 0x41, 0x37, 0xfd, 0xef, 0x75, 0xdc, 0x31, + 0x1d, 0x5e, 0x79, 0x08, 0x83, 0x8c, 0xee, 0x63, 0xae, 0xa6, 0xf3, 0xe9, 0x62, 0xb6, 0x5c, 0xe8, + 0x6a, 0xe8, 0x40, 0x6b, 0xb6, 0x60, 0x9d, 0x4a, 0xe6, 0xf8, 0xeb, 0x6c, 0xca, 0xf4, 0xcc, 0x56, + 0xb3, 0x6f, 0xce, 0x8f, 0x16, 0x7d, 0x0d, 0x05, 0x15, 0xa6, 0xe2, 0x79, 0x98, 0x98, 0x37, 0xa9, + 0xcd, 0x71, 0xe1, 0x3b, 0x82, 0xd1, 0x2a, 0x27, 0x4f, 0x58, 0xcd, 0x16, 0x8e, 0x2b, 0xa5, 0x0c, + 0xc3, 0x9e, 0x11, 0x57, 0x51, 0x3e, 0xdd, 0x33, 0xc3, 0x00, 0xbc, 0x6a, 0x8a, 0x0f, 0x20, 0xe3, + 0x68, 0x55, 0xd3, 0x79, 0x54, 0x1c, 0x2f, 0xcf, 0xf7, 0xb3, 0x73, 0xe4, 0x99, 0xae, 0x85, 0x72, + 0x1f, 0x46, 0x6a, 0x9e, 0x14, 0xcc, 0xd5, 0x4c, 0x1f, 0x99, 0x21, 0xba, 0x3a, 0xea, 0x14, 0x25, + 0x90, 0x5d, 0x98, 0x82, 0x89, 0x68, 0xea, 0xb2, 0x26, 0x3f, 0x10, 0x8c, 0xb9, 0xe5, 0x6a, 0xd3, + 0x3a, 0xfe, 0xdf, 0x8a, 0x32, 0x0d, 0x93, 0xb1, 0xdc, 0x65, 0x55, 0xde, 0x22, 0xb8, 0x5e, 0xe5, + 0x64, 0xd3, 0x66, 0xff, 0x60, 0x1c, 0xe2, 0xed, 0xac, 0x81, 0x7a, 0x51, 0x89, 0x94, 0xf9, 0x1e, + 0xf9, 0x09, 0x78, 0x0e, 0x2a, 0xad, 0xfd, 0xfa, 0x66, 0x73, 0xb7, 0x26, 0xf0, 0x15, 0xb4, 0xae, + 0xc1, 0x30, 0xb6, 0x05, 0xb3, 0x30, 0x57, 0x07, 0xdc, 0x29, 0x5c, 0xe8, 0xa7, 0x37, 0x3a, 0x88, + 0x81, 0x6d, 0x5c, 0xfb, 0x2c, 0xdc, 0xbc, 0x54, 0x9e, 0x4c, 0xe0, 0x33, 0x82, 0x6c, 0x95, 0x93, + 0x0d, 0xec, 0x36, 0x25, 0xff, 0x9b, 0xbd, 0xb7, 0x0e, 0xa3, 0x4e, 0x27, 0x6d, 0xb5, 0x5c, 0x45, + 0x89, 0x56, 0x8f, 0x27, 0xde, 0xcf, 0x38, 0xcb, 0xe4, 0x9f, 0x8b, 0x6d, 0xf5, 0x0a, 0x20, 0xc4, + 0x65, 0x93, 0xa3, 0x3f, 0x6b, 0xf2, 0x81, 0xc4, 0x4d, 0x5e, 0x98, 0x84, 0x1b, 0x91, 0xa2, 0x06, + 0xc5, 0x2e, 0xff, 0xcc, 0x40, 0xba, 0xca, 0x89, 0x82, 0x21, 0x1b, 0xdd, 0xf2, 0xb7, 0xba, 0x29, + 0x8a, 0x6f, 0x51, 0x4d, 0x4f, 0xc6, 0x05, 0xe1, 0x94, 0x2d, 0x18, 0x09, 0x37, 0xed, 0x7c, 0x0f, + 0x63, 0x49, 0x69, 0x77, 0x92, 0x50, 0x32, 0xc0, 0x36, 0x40, 0x64, 0x6d, 0x2d, 0xf4, 0x94, 0x17, + 0x60, 0x5a, 0x29, 0x11, 0x26, 0x63, 0xd4, 0x61, 0x2c, 0xbe, 0x04, 0x8a, 0x3d, 0xec, 0x63, 0xa4, + 0xb6, 0x94, 0x94, 0x94, 0xc1, 0x0e, 0x41, 0xb9, 0x64, 0x94, 0x4b, 0x7d, 0xeb, 0x1e, 0xc5, 0xb5, + 0x95, 0xdf, 0xc2, 0x65, 0xec, 0x97, 0x70, 0x4d, 0x4e, 0xe1, 0x5c, 0x0f, 0x17, 0x01, 0xa4, 0xdd, + 0x4e, 0x00, 0x05, 0xde, 0xb5, 0xc1, 0xd7, 0xe7, 0x47, 0x8b, 0xa8, 0x52, 0x3f, 0x3e, 0xcd, 0xa1, + 0x93, 0xd3, 0x1c, 0xfa, 0x76, 0x9a, 0x43, 0xef, 0xce, 0x72, 0xa9, 0x93, 0xb3, 0x5c, 0xea, 0xcb, + 0x59, 0x2e, 0x05, 0x33, 0x16, 0xed, 0xe2, 0xef, 0x19, 0x7a, 0x71, 0x8f, 0x58, 0x62, 0xaf, 0xb5, + 0xad, 0xef, 0xd0, 0x86, 0x11, 0x42, 0x25, 0x8b, 0x46, 0x4e, 0xc6, 0x41, 0xf8, 0xc2, 0x11, 0x9d, + 0x26, 0xe6, 0xdb, 0x43, 0xee, 0xbb, 0xe6, 0xee, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x99, 0xf7, + 0x2e, 0x80, 0xaf, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -633,9 +809,11 @@ type MsgClient interface { RegisterNFT(ctx context.Context, in *MsgRegisterNFT, opts ...grpc.CallOption) (*MsgRegisterNFTResponse, error) // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. + // Multiple signers are supported for multi-party authorization workflows. GrantRole(ctx context.Context, in *MsgGrantRole, opts ...grpc.CallOption) (*MsgGrantRoleResponse, error) // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. + // Multiple signers are supported for multi-party authorization workflows. RevokeRole(ctx context.Context, in *MsgRevokeRole, opts ...grpc.CallOption) (*MsgRevokeRoleResponse, error) // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry for the specified key. @@ -644,6 +822,9 @@ type MsgClient interface { // This creates multiple registry entries, or updates if one exists. // Each registry in this will cost one MsgRegisterNFT. RegistryBulkUpdate(ctx context.Context, in *MsgRegistryBulkUpdate, opts ...grpc.CallOption) (*MsgRegistryBulkUpdateResponse, error) + // SetRoles atomically sets the desired state for one or more roles on a registry entry. + // Multiple signers are supported for multi-party authorization workflows. + SetRoles(ctx context.Context, in *MsgSetRoles, opts ...grpc.CallOption) (*MsgSetRolesResponse, error) } type msgClient struct { @@ -699,6 +880,15 @@ func (c *msgClient) RegistryBulkUpdate(ctx context.Context, in *MsgRegistryBulkU return out, nil } +func (c *msgClient) SetRoles(ctx context.Context, in *MsgSetRoles, opts ...grpc.CallOption) (*MsgSetRolesResponse, error) { + out := new(MsgSetRolesResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/SetRoles", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // RegisterNFT registers a new NFT in the registry. @@ -706,9 +896,11 @@ type MsgServer interface { RegisterNFT(context.Context, *MsgRegisterNFT) (*MsgRegisterNFTResponse, error) // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. + // Multiple signers are supported for multi-party authorization workflows. GrantRole(context.Context, *MsgGrantRole) (*MsgGrantRoleResponse, error) // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. + // Multiple signers are supported for multi-party authorization workflows. RevokeRole(context.Context, *MsgRevokeRole) (*MsgRevokeRoleResponse, error) // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry for the specified key. @@ -717,6 +909,9 @@ type MsgServer interface { // This creates multiple registry entries, or updates if one exists. // Each registry in this will cost one MsgRegisterNFT. RegistryBulkUpdate(context.Context, *MsgRegistryBulkUpdate) (*MsgRegistryBulkUpdateResponse, error) + // SetRoles atomically sets the desired state for one or more roles on a registry entry. + // Multiple signers are supported for multi-party authorization workflows. + SetRoles(context.Context, *MsgSetRoles) (*MsgSetRolesResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -738,6 +933,9 @@ func (*UnimplementedMsgServer) UnregisterNFT(ctx context.Context, req *MsgUnregi func (*UnimplementedMsgServer) RegistryBulkUpdate(ctx context.Context, req *MsgRegistryBulkUpdate) (*MsgRegistryBulkUpdateResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegistryBulkUpdate not implemented") } +func (*UnimplementedMsgServer) SetRoles(ctx context.Context, req *MsgSetRoles) (*MsgSetRolesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method SetRoles not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -833,6 +1031,24 @@ func _Msg_RegistryBulkUpdate_Handler(srv interface{}, ctx context.Context, dec f return interceptor(ctx, in, info, handler) } +func _Msg_SetRoles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgSetRoles) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).SetRoles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/SetRoles", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).SetRoles(ctx, req.(*MsgSetRoles)) + } + return interceptor(ctx, in, info, handler) +} + var Msg_serviceDesc = _Msg_serviceDesc var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Msg", @@ -858,6 +1074,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "RegistryBulkUpdate", Handler: _Msg_RegistryBulkUpdate_Handler, }, + { + MethodName: "SetRoles", + Handler: _Msg_SetRoles_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/tx.proto", @@ -988,12 +1208,14 @@ func (m *MsgGrantRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- - dAtA[i] = 0xa + if len(m.Signers) > 0 { + for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Signers[iNdEx]) + copy(dAtA[i:], m.Signers[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signers[iNdEx]))) + i-- + dAtA[i] = 0xa + } } return len(dAtA) - i, nil } @@ -1067,12 +1289,14 @@ func (m *MsgRevokeRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Signer) > 0 { - i -= len(m.Signer) - copy(dAtA[i:], m.Signer) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) - i-- - dAtA[i] = 0xa + if len(m.Signers) > 0 { + for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Signers[iNdEx]) + copy(dAtA[i:], m.Signers[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signers[iNdEx]))) + i-- + dAtA[i] = 0xa + } } return len(dAtA) - i, nil } @@ -1232,6 +1456,124 @@ func (m *MsgRegistryBulkUpdateResponse) MarshalToSizedBuffer(dAtA []byte) (int, return len(dAtA) - i, nil } +func (m *MsgSetRoles) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetRoles) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetRoles) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RoleUpdates) > 0 { + for iNdEx := len(m.RoleUpdates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleUpdates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if m.Key != nil { + { + size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signers) > 0 { + for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Signers[iNdEx]) + copy(dAtA[i:], m.Signers[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signers[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *RoleUpdate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleUpdate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Addresses) > 0 { + for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addresses[iNdEx]) + copy(dAtA[i:], m.Addresses[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Role != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Role)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *MsgSetRolesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgSetRolesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgSetRolesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -1281,9 +1623,11 @@ func (m *MsgGrantRole) Size() (n int) { } var l int _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) + if len(m.Signers) > 0 { + for _, s := range m.Signers { + l = len(s) + n += 1 + l + sovTx(uint64(l)) + } } if m.Key != nil { l = m.Key.Size() @@ -1316,9 +1660,11 @@ func (m *MsgRevokeRole) Size() (n int) { } var l int _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) + if len(m.Signers) > 0 { + for _, s := range m.Signers { + l = len(s) + n += 1 + l + sovTx(uint64(l)) + } } if m.Key != nil { l = m.Key.Size() @@ -1399,13 +1745,65 @@ func (m *MsgRegistryBulkUpdateResponse) Size() (n int) { return n } -func sovTx(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozTx(x uint64) (n int) { - return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *MsgRegisterNFT) Unmarshal(dAtA []byte) error { +func (m *MsgSetRoles) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Signers) > 0 { + for _, s := range m.Signers { + l = len(s) + n += 1 + l + sovTx(uint64(l)) + } + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovTx(uint64(l)) + } + if len(m.RoleUpdates) > 0 { + for _, e := range m.RoleUpdates { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *RoleUpdate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Role != 0 { + n += 1 + sovTx(uint64(m.Role)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { + l = len(s) + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgSetRolesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgRegisterNFT) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1638,7 +2036,7 @@ func (m *MsgGrantRole) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1666,7 +2064,7 @@ func (m *MsgGrantRole) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signer = string(dAtA[iNdEx:postIndex]) + m.Signers = append(m.Signers, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 2 { @@ -1857,7 +2255,7 @@ func (m *MsgRevokeRole) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1885,7 +2283,7 @@ func (m *MsgRevokeRole) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signer = string(dAtA[iNdEx:postIndex]) + m.Signers = append(m.Signers, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 2: if wireType != 2 { @@ -2379,6 +2777,309 @@ func (m *MsgRegistryBulkUpdateResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgSetRoles) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetRoles: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetRoles: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signers = append(m.Signers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Key == nil { + m.Key = &RegistryKey{} + } + if err := m.Key.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleUpdates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleUpdates = append(m.RoleUpdates, RoleUpdate{}) + if err := m.RoleUpdates[len(m.RoleUpdates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RoleUpdate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoleUpdate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoleUpdate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + m.Role = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Role |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgSetRolesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgSetRolesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgSetRolesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 3b1fc743fe45f5fd4e05434d908f0a325e26a72d Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 18 Jun 2026 11:20:39 -0600 Subject: [PATCH 02/36] acceptance tests --- .../registry/v1/authorization.proto | 2 +- x/registry/keeper/authorization.go | 5 +- .../keeper/authorization_acceptance_test.go | 305 +++ x/registry/types/authorization.go | 30 +- x/registry/types/authorization.pb.go | 1919 +++++++++-------- 5 files changed, 1392 insertions(+), 869 deletions(-) create mode 100644 x/registry/keeper/authorization_acceptance_test.go diff --git a/proto/provenance/registry/v1/authorization.proto b/proto/provenance/registry/v1/authorization.proto index 46404f4a37..7f031e8039 100644 --- a/proto/provenance/registry/v1/authorization.proto +++ b/proto/provenance/registry/v1/authorization.proto @@ -30,7 +30,7 @@ enum SignatureType { enum NftRole { // NFT_ROLE_UNSPECIFIED is the default/unset value. NFT_ROLE_UNSPECIFIED = 0; - // NFT_ROLE_NFT_OWNER is the owner of the NFT (value owner from the metadata module scope). + // NFT_ROLE_NFT_OWNER is the owner of the NFT (value owner from the metadata module Scope). NFT_ROLE_NFT_OWNER = 1; } diff --git a/x/registry/keeper/authorization.go b/x/registry/keeper/authorization.go index ced2ae04b3..ec1c023ab3 100644 --- a/x/registry/keeper/authorization.go +++ b/x/registry/keeper/authorization.go @@ -98,7 +98,10 @@ func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.R switch req.Type { case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL: for _, ra := range req.Roles { - addrs, _ := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + addrs, exists := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if !exists || len(addrs) == 0 { + return types.NewErrCodeUnauthorized("required role resolved to no addresses") + } for _, addr := range addrs { if !signerSet[addr] { return types.NewErrCodeUnauthorized(fmt.Sprintf("missing required signature for %q", addr)) diff --git a/x/registry/keeper/authorization_acceptance_test.go b/x/registry/keeper/authorization_acceptance_test.go new file mode 100644 index 0000000000..10a9b49a1e --- /dev/null +++ b/x/registry/keeper/authorization_acceptance_test.go @@ -0,0 +1,305 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/x/nft" + nftkeeper "cosmossdk.io/x/nft/keeper" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// ControllerAuthAcceptanceTestSuite implements the acceptance tests from the ticket +// (sc-512248) that verify the static multi-signer authorization rules for updating +// the CONTROLLER role. +// +// Required signatures for a Controller update: +// - Current Controller +// - Current Secured Party for eNote (only if set) +// - New Controller +type ControllerAuthAcceptanceTestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + registryKeeper keeper.Keeper + nftKeeper nftkeeper.Keeper + msgServer types.MsgServer + + nftClass nft.Class + + // nftOwner owns the NFT and is the fallback authority when no controller is set. + nftOwner string + + currentController string + newController string + securedParty string + stranger string +} + +func TestControllerAuthAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(ControllerAuthAcceptanceTestSuite)) +} + +func (s *ControllerAuthAcceptanceTestSuite) SetupTest() { + s.app = app.Setup(s.T()) + s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + + s.nftKeeper = s.app.NFTKeeper + s.registryKeeper = s.app.RegistryKeeper + s.msgServer = keeper.NewMsgServer(s.registryKeeper) + + s.nftOwner = genAddr() + s.currentController = genAddr() + s.newController = genAddr() + s.securedParty = genAddr() + s.stranger = genAddr() + + s.nftClass = nft.Class{Id: "test-nft-class-id"} + s.nftKeeper.SaveClass(s.ctx, s.nftClass) +} + +// genAddr generates a fresh bech32 account address. +func genAddr() string { + return sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() +} + +// mintNFT mints an NFT with the given id owned by nftOwner and returns its registry key. +func (s *ControllerAuthAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { + n := nft.NFT{ClassId: s.nftClass.Id, Id: id} + ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) + s.Require().NoError(err) + s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) + return &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} +} + +// setupRegistry creates a registry entry for the key with the provided roles. +func (s *ControllerAuthAcceptanceTestSuite) setupRegistry(key *types.RegistryKey, roles []types.RolesEntry) { + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles)) +} + +// grantController attempts to grant the CONTROLLER role to newController, signed by the +// provided signers, and returns the resulting error (nil on success). +func (s *ControllerAuthAcceptanceTestSuite) grantController(key *types.RegistryKey, signers []string) error { + _, err := s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signers: signers, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: []string{s.newController}, + }) + return err +} + +// roleAddresses returns the addresses currently assigned to the given role on the entry. +func (s *ControllerAuthAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { + entry, err := s.registryKeeper.GetRegistry(s.ctx, key) + s.Require().NoError(err) + s.Require().NotNil(entry) + for _, re := range entry.Roles { + if re.Role == role { + return re.Addresses + } + } + return nil +} + +// --- Scenario A: Controller set, no Secured Party for eNote --------------------------- + +func (s *ControllerAuthAcceptanceTestSuite) TestControllerSet_NoSecuredParty() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + + baseRoles := func() []types.RolesEntry { + return []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + } + } + + s.Run("happy path: current controller and new controller sign", func() { + key := s.mintNFT("nft-a-happy") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.currentController, s.newController}) + s.Require().NoError(err, "current + new controller should authorize the update") + + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController, + "new controller should be added to the CONTROLLER role") + }) + + s.Run("reject: only current controller signs", func() { + key := s.mintNFT("nft-a-cc-only") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.currentController}) + s.Require().Error(err, "missing new controller signature should be rejected") + }) + + s.Run("reject: only new controller signs", func() { + key := s.mintNFT("nft-a-nc-only") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.newController}) + s.Require().Error(err, "missing current controller signature should be rejected") + }) +} + +// --- Scenario B: Controller set, Secured Party for eNote set -------------------------- + +func (s *ControllerAuthAcceptanceTestSuite) TestControllerSet_WithSecuredParty() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + baseRoles := func() []types.RolesEntry { + return []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + } + } + + s.Run("happy path: current controller, secured party, and new controller sign", func() { + key := s.mintNFT("nft-b-happy") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.currentController, s.securedParty, s.newController}) + s.Require().NoError(err, "all three required signers should authorize the update") + + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController, + "new controller should be added to the CONTROLLER role") + }) + + s.Run("reject: only current controller signs", func() { + key := s.mintNFT("nft-b-cc-only") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.currentController}) + s.Require().Error(err) + }) + + s.Run("reject: only new controller signs", func() { + key := s.mintNFT("nft-b-nc-only") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.newController}) + s.Require().Error(err) + }) + + s.Run("reject: only secured party signs", func() { + key := s.mintNFT("nft-b-sp-only") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.securedParty}) + s.Require().Error(err) + }) + + s.Run("reject: current controller and secured party sign (missing new controller)", func() { + key := s.mintNFT("nft-b-cc-sp") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.currentController, s.securedParty}) + s.Require().Error(err) + }) + + s.Run("reject: current controller and new controller sign (missing secured party)", func() { + key := s.mintNFT("nft-b-cc-nc") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.currentController, s.newController}) + s.Require().Error(err, "secured party signature is required when it is set") + }) + + s.Run("reject: secured party and new controller sign (missing current controller)", func() { + key := s.mintNFT("nft-b-sp-nc") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.securedParty, s.newController}) + s.Require().Error(err, "current controller signature is always required (no unilateral takeover)") + }) + + s.Run("reject: an unrelated stranger signs", func() { + key := s.mintNFT("nft-b-stranger") + s.setupRegistry(key, baseRoles()) + + err := s.grantController(key, []string{s.stranger}) + s.Require().Error(err) + }) +} + +// --- AuthZ grant scenarios ------------------------------------------------------------ +// +// The ticket asks us to verify the same scenarios "but signed with authz grants". These tests +// document a hard architectural constraint discovered during the PoC: the Cosmos SDK authz +// module can only dispatch messages that have exactly one signer (see +// x/authz/keeper/keeper.go DispatchActions, which returns ErrAuthorizationNumOfSigners when +// len(signers) != 1). +// +// Because a Controller update always requires at least two signers (current controller and new +// controller, plus the secured party when set), a multi-signer MsgGrantRole cannot be executed +// through an authz MsgExec at all. Delegation via authz would therefore require a different +// multisig model (e.g. a single multisig account/key whose message has one signer), which is out +// of scope for this PoC. + +// mustAddr converts a bech32 string into an sdk.AccAddress, failing the test on error. +func (s *ControllerAuthAcceptanceTestSuite) mustAddr(addr string) sdk.AccAddress { + a, err := sdk.AccAddressFromBech32(addr) + s.Require().NoError(err) + return a +} + +// grantGrantRoleAuthz grants the executor authority to execute MsgGrantRole on behalf of granter. +func (s *ControllerAuthAcceptanceTestSuite) grantGrantRoleAuthz(executor, granter string) { + auth := authz.NewGenericAuthorization(sdk.MsgTypeURL(&types.MsgGrantRole{})) + s.Require().NoError(s.app.AuthzKeeper.SaveGrant(s.ctx, s.mustAddr(executor), s.mustAddr(granter), auth, nil)) +} + +// execGrantControllerViaAuthz submits a MsgGrantRole (with the given signers) wrapped in a +// MsgExec run by executor, and returns the resulting error. +func (s *ControllerAuthAcceptanceTestSuite) execGrantControllerViaAuthz(executor string, key *types.RegistryKey, signers []string) error { + inner := &types.MsgGrantRole{ + Signers: signers, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: []string{s.newController}, + } + msgExec := authz.NewMsgExec(s.mustAddr(executor), []sdk.Msg{inner}) + _, err := s.app.AuthzKeeper.Exec(s.ctx, &msgExec) + return err +} + +func (s *ControllerAuthAcceptanceTestSuite) TestControllerUpdate_ViaAuthzGrants() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + executor := genAddr() + + s.Run("authz cannot dispatch a multi-signer controller update", func() { + key := s.mintNFT("nft-authz-multisig") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + // Even with grants from every required party, authz refuses to dispatch because the + // inner MsgGrantRole has more than one signer. + s.grantGrantRoleAuthz(executor, s.currentController) + s.grantGrantRoleAuthz(executor, s.securedParty) + s.grantGrantRoleAuthz(executor, s.newController) + + err := s.execGrantControllerViaAuthz(executor, key, []string{s.currentController, s.securedParty, s.newController}) + s.Require().Error(err, "authz must reject a multi-signer message") + s.Require().ErrorIs(err, authz.ErrAuthorizationNumOfSigners, + "authz rejects multi-signer messages with ErrAuthorizationNumOfSigners") + + // The controller role must remain unchanged since the update never executed. + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) +} diff --git a/x/registry/types/authorization.go b/x/registry/types/authorization.go index 7fbc06c07e..a91e50eeee 100644 --- a/x/registry/types/authorization.go +++ b/x/registry/types/authorization.go @@ -21,12 +21,13 @@ func RoleAuthorizationMap() map[RegistryRole]RoleAuthorization { // controllerRoleAuthorization defines the CONTROLLER role authorization policy. // -// Path 1 – Standard transfer: The current controller (or NFT owner if no controller is set) must -// sign. Additionally, the current Secured Party for eNote and the incoming new controller must -// each sign if they are set. +// A Controller update requires all of the following to sign: +// - the current controller (or the NFT owner if no controller is set); +// - the current Secured Party for eNote, if one is set; +// - the incoming new controller. // -// Path 2 – Foreclosure: The current Secured Party for eNote and the new controller both sign, -// allowing the secured party to unilaterally assume control in case of default. +// The current controller's signature is always required: there is no path that lets any other +// party assume control unilaterally. func controllerRoleAuthorization() RoleAuthorization { return RoleAuthorization{ Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, @@ -68,25 +69,6 @@ func controllerRoleAuthorization() RoleAuthorization { }, }, }, - { - Description: "Foreclosure: Secured Party for eNote can unilaterally become Controller in case of default", - Signatures: []*SignatureRequirement{ - { - // The Secured Party for eNote and the incoming new controller must both sign. - Type: SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, - Roles: []*RoleAssignment{ - { - RoleSelector: &RoleAssignment_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE}, - Assignment: Assignment_ASSIGNMENT_CURRENT, - }, - { - RoleSelector: &RoleAssignment_RegistryRole{RegistryRole: RegistryRole_REGISTRY_ROLE_CONTROLLER}, - Assignment: Assignment_ASSIGNMENT_NEW, - }, - }, - }, - }, - }, }, } } diff --git a/x/registry/types/authorization.pb.go b/x/registry/types/authorization.pb.go index 6a78c39b82..e0d1b00108 100644 --- a/x/registry/types/authorization.pb.go +++ b/x/registry/types/authorization.pb.go @@ -1,24 +1,44 @@ -// Manually written to match protoc-gen-gogo output for provenance/registry/v1/authorization.proto. -// Should be regenerated with protoc when the proto builder is available. +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: provenance/registry/v1/authorization.proto package types import ( - "fmt" + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" io "io" + math "math" math_bits "math/bits" - - proto "github.com/cosmos/gogoproto/proto" ) +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + // SignatureType defines how signature requirements are evaluated. type SignatureType int32 const ( - SignatureType_SIGNATURE_TYPE_UNSPECIFIED SignatureType = 0 - SignatureType_SIGNATURE_TYPE_REQUIRED_ALL SignatureType = 1 + // SIGNATURE_TYPE_UNSPECIFIED is the default/unset value. + SignatureType_SIGNATURE_TYPE_UNSPECIFIED SignatureType = 0 + // SIGNATURE_TYPE_REQUIRED_ALL requires signatures from ALL listed roles. + // Fails immediately if any role does not exist in the registry or NFT state. + SignatureType_SIGNATURE_TYPE_REQUIRED_ALL SignatureType = 1 + // SIGNATURE_TYPE_REQUIRED_ALL_IF_SET requires signatures from ALL listed roles, + // but only if they exist. Roles that are not set are silently skipped. SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET SignatureType = 2 - SignatureType_SIGNATURE_TYPE_REQUIRED_ANY SignatureType = 3 + // SIGNATURE_TYPE_REQUIRED_ANY requires a signature from AT LEAST ONE of the listed roles. + // Fails if none of the roles exist. + SignatureType_SIGNATURE_TYPE_REQUIRED_ANY SignatureType = 3 + // SIGNATURE_TYPE_REQUIRED_ANY_IF_SET requires a signature from AT LEAST ONE of the listed roles, + // but only if any of them exist. Skips the entire requirement if none of the roles are set. SignatureType_SIGNATURE_TYPE_REQUIRED_ANY_IF_SET SignatureType = 4 ) @@ -42,12 +62,19 @@ func (x SignatureType) String() string { return proto.EnumName(SignatureType_name, int32(x)) } -// NftRole identifies roles managed outside the registry module. +func (SignatureType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{0} +} + +// NftRole identifies roles managed outside the registry module (e.g., by the metadata module). +// These roles are read-only from the registry module's perspective. type NftRole int32 const ( + // NFT_ROLE_UNSPECIFIED is the default/unset value. NftRole_NFT_ROLE_UNSPECIFIED NftRole = 0 - NftRole_NFT_ROLE_NFT_OWNER NftRole = 1 + // NFT_ROLE_NFT_OWNER is the owner of the NFT (value owner from the metadata module Scope). + NftRole_NFT_ROLE_NFT_OWNER NftRole = 1 ) var NftRole_name = map[int32]string{ @@ -64,17 +91,36 @@ func (x NftRole) String() string { return proto.EnumName(NftRole_name, int32(x)) } -// Assignment describes which address(es) to resolve for a role. +func (NftRole) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{1} +} + +// Assignment describes which address(es) to resolve for a role in an authorization check. type Assignment int32 const ( + // ASSIGNMENT_UNSPECIFIED is the default/unset value. Assignment_ASSIGNMENT_UNSPECIFIED Assignment = 0 - Assignment_ASSIGNMENT_CURRENT Assignment = 1 + // ASSIGNMENT_CURRENT resolves exactly one address currently assigned to the role. + // Fails if the role has multiple addresses assigned. + Assignment_ASSIGNMENT_CURRENT Assignment = 1 + // ASSIGNMENT_CURRENT_ALL resolves all addresses currently assigned to the role. + // All of them must sign. Assignment_ASSIGNMENT_CURRENT_ALL Assignment = 2 + // ASSIGNMENT_CURRENT_ANY resolves any one of the addresses currently assigned to the role. + // At least one must sign. Assignment_ASSIGNMENT_CURRENT_ANY Assignment = 3 - Assignment_ASSIGNMENT_NEW Assignment = 4 - Assignment_ASSIGNMENT_NEW_ANY Assignment = 5 - Assignment_ASSIGNMENT_NEW_ALL Assignment = 6 + // ASSIGNMENT_NEW resolves exactly one new address being assigned to the role. + // Fails if multiple new addresses are provided. + // Invalid for NftRole (read-only from registry perspective). + Assignment_ASSIGNMENT_NEW Assignment = 4 + // ASSIGNMENT_NEW_ANY resolves any one of the new addresses being assigned to the role. + // Invalid for NftRole. + Assignment_ASSIGNMENT_NEW_ANY Assignment = 5 + // ASSIGNMENT_NEW_ALL resolves all new addresses being assigned to the role. + // All of them must sign. + // Invalid for NftRole. + Assignment_ASSIGNMENT_NEW_ALL Assignment = 6 ) var Assignment_name = map[int32]string{ @@ -101,48 +147,50 @@ func (x Assignment) String() string { return proto.EnumName(Assignment_name, int32(x)) } -func init() { - proto.RegisterEnum("provenance.registry.v1.SignatureType", SignatureType_name, SignatureType_value) - proto.RegisterEnum("provenance.registry.v1.NftRole", NftRole_name, NftRole_value) - proto.RegisterEnum("provenance.registry.v1.Assignment", Assignment_name, Assignment_value) - proto.RegisterType((*RoleAuthorization)(nil), "provenance.registry.v1.RoleAuthorization") - proto.RegisterType((*Authorization)(nil), "provenance.registry.v1.Authorization") - proto.RegisterType((*SignatureRequirement)(nil), "provenance.registry.v1.SignatureRequirement") - proto.RegisterType((*RoleAssignment)(nil), "provenance.registry.v1.RoleAssignment") - proto.RegisterType((*RolePriority)(nil), "provenance.registry.v1.RolePriority") - proto.RegisterType((*RolePriorityEntry)(nil), "provenance.registry.v1.RolePriorityEntry") +func (Assignment) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{2} } -// ----------------------------------------------------------------------- -// RoleAuthorization -// ----------------------------------------------------------------------- - // RoleAuthorization configures who must sign to update a specific role. +// Contains one or more Authorization paths; the update is approved if ANY path is fully satisfied. type RoleAuthorization struct { - // role is the registry role being controlled. + // role is the registry role whose assignments are being controlled. Role RegistryRole `protobuf:"varint,1,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` // authorizations is a list of alternative approval paths. - Authorizations []Authorization `protobuf:"bytes,2,rep,name=authorizations,proto3" json:"authorizations"` + // The role update is approved if any single authorization is fully satisfied. + Authorizations []*Authorization `protobuf:"bytes,2,rep,name=authorizations,proto3" json:"authorizations,omitempty"` } func (m *RoleAuthorization) Reset() { *m = RoleAuthorization{} } func (m *RoleAuthorization) String() string { return proto.CompactTextString(m) } func (*RoleAuthorization) ProtoMessage() {} -func (m *RoleAuthorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (*RoleAuthorization) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{0} +} +func (m *RoleAuthorization) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} func (m *RoleAuthorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_RoleAuthorization.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil } - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil } -func (m *RoleAuthorization) XXX_Merge(src proto.Message) { xxx_messageInfo_RoleAuthorization.Merge(m, src) } -func (m *RoleAuthorization) XXX_Size() int { return m.Size() } -func (m *RoleAuthorization) XXX_DiscardUnknown() { xxx_messageInfo_RoleAuthorization.DiscardUnknown(m) } +func (m *RoleAuthorization) XXX_Merge(src proto.Message) { + xxx_messageInfo_RoleAuthorization.Merge(m, src) +} +func (m *RoleAuthorization) XXX_Size() int { + return m.Size() +} +func (m *RoleAuthorization) XXX_DiscardUnknown() { + xxx_messageInfo_RoleAuthorization.DiscardUnknown(m) +} var xxx_messageInfo_RoleAuthorization proto.InternalMessageInfo @@ -153,200 +201,52 @@ func (m *RoleAuthorization) GetRole() RegistryRole { return RegistryRole_REGISTRY_ROLE_UNSPECIFIED } -func (m *RoleAuthorization) GetAuthorizations() []Authorization { +func (m *RoleAuthorization) GetAuthorizations() []*Authorization { if m != nil { return m.Authorizations } return nil } -func (m *RoleAuthorization) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RoleAuthorization) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RoleAuthorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Authorizations) > 0 { - for iNdEx := len(m.Authorizations) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Authorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthorization(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 - } - } - if m.Role != 0 { - i = encodeVarintAuthorization(dAtA, i, uint64(m.Role)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - -func (m *RoleAuthorization) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Role != 0 { - n += 1 + sovAuthorization(uint64(m.Role)) - } - for _, e := range m.Authorizations { - l = e.Size() - n += 1 + l + sovAuthorization(uint64(l)) - } - return n -} - -func (m *RoleAuthorization) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthorization - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RoleAuthorization: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RoleAuthorization: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) - } - m.Role = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthorization - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Role |= RegistryRole(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Authorizations", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowAuthorization - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthAuthorization - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLengthAuthorization - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Authorizations = append(m.Authorizations, Authorization{}) - if err := m.Authorizations[len(m.Authorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipAuthorization(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthAuthorization - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -// ----------------------------------------------------------------------- -// Authorization -// ----------------------------------------------------------------------- - // Authorization defines one complete approval path for a role update. +// All signature requirements in this authorization must be satisfied for this path to succeed. type Authorization struct { - Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` - Signatures []SignatureRequirement `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures"` + // description is a human-readable explanation of this authorization path. + Description string `protobuf:"bytes,1,opt,name=description,proto3" json:"description,omitempty"` + // signatures is the ordered list of signature requirements that must all be satisfied. + Signatures []*SignatureRequirement `protobuf:"bytes,2,rep,name=signatures,proto3" json:"signatures,omitempty"` } func (m *Authorization) Reset() { *m = Authorization{} } func (m *Authorization) String() string { return proto.CompactTextString(m) } func (*Authorization) ProtoMessage() {} -func (m *Authorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } +func (*Authorization) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{1} +} +func (m *Authorization) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} func (m *Authorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { return xxx_messageInfo_Authorization.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil } - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil } -func (m *Authorization) XXX_Merge(src proto.Message) { xxx_messageInfo_Authorization.Merge(m, src) } -func (m *Authorization) XXX_Size() int { return m.Size() } -func (m *Authorization) XXX_DiscardUnknown() { xxx_messageInfo_Authorization.DiscardUnknown(m) } +func (m *Authorization) XXX_Merge(src proto.Message) { + xxx_messageInfo_Authorization.Merge(m, src) +} +func (m *Authorization) XXX_Size() int { + return m.Size() +} +func (m *Authorization) XXX_DiscardUnknown() { + xxx_messageInfo_Authorization.DiscardUnknown(m) +} var xxx_messageInfo_Authorization proto.InternalMessageInfo @@ -357,73 +257,843 @@ func (m *Authorization) GetDescription() string { return "" } -func (m *Authorization) GetSignatures() []SignatureRequirement { +func (m *Authorization) GetSignatures() []*SignatureRequirement { if m != nil { return m.Signatures } return nil } -func (m *Authorization) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil +// SignatureRequirement defines a single signature check within an authorization path. +type SignatureRequirement struct { + // type controls how the roles are evaluated (required_all, required_all_if_set, etc.). + Type SignatureType `protobuf:"varint,1,opt,name=type,proto3,enum=provenance.registry.v1.SignatureType" json:"type,omitempty"` + // roles lists the role assignments that must be checked. + Roles []*RoleAssignment `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles,omitempty"` } -func (m *Authorization) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) +func (m *SignatureRequirement) Reset() { *m = SignatureRequirement{} } +func (m *SignatureRequirement) String() string { return proto.CompactTextString(m) } +func (*SignatureRequirement) ProtoMessage() {} +func (*SignatureRequirement) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{2} } - -func (m *Authorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Signatures) > 0 { - for iNdEx := len(m.Signatures) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Signatures[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthorization(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 +func (m *SignatureRequirement) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SignatureRequirement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SignatureRequirement.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err } + return b[:n], nil } - if len(m.Description) > 0 { - i -= len(m.Description) - copy(dAtA[i:], m.Description) - i = encodeVarintAuthorization(dAtA, i, uint64(len(m.Description))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil +} +func (m *SignatureRequirement) XXX_Merge(src proto.Message) { + xxx_messageInfo_SignatureRequirement.Merge(m, src) +} +func (m *SignatureRequirement) XXX_Size() int { + return m.Size() +} +func (m *SignatureRequirement) XXX_DiscardUnknown() { + xxx_messageInfo_SignatureRequirement.DiscardUnknown(m) } -func (m *Authorization) Size() (n int) { - if m == nil { - return 0 +var xxx_messageInfo_SignatureRequirement proto.InternalMessageInfo + +func (m *SignatureRequirement) GetType() SignatureType { + if m != nil { + return m.Type } - var l int + return SignatureType_SIGNATURE_TYPE_UNSPECIFIED +} + +func (m *SignatureRequirement) GetRoles() []*RoleAssignment { + if m != nil { + return m.Roles + } + return nil +} + +// RoleAssignment specifies which role and which assignment type to resolve for a signature check. +type RoleAssignment struct { + // role_selector identifies the role to resolve, either a single role or a priority list. + // + // Types that are valid to be assigned to RoleSelector: + // + // *RoleAssignment_RegistryRole + // *RoleAssignment_NftRole + // *RoleAssignment_RolePriority + RoleSelector isRoleAssignment_RoleSelector `protobuf_oneof:"role_selector"` + // assignment specifies which address(es) to resolve for the role. + Assignment Assignment `protobuf:"varint,4,opt,name=assignment,proto3,enum=provenance.registry.v1.Assignment" json:"assignment,omitempty"` +} + +func (m *RoleAssignment) Reset() { *m = RoleAssignment{} } +func (m *RoleAssignment) String() string { return proto.CompactTextString(m) } +func (*RoleAssignment) ProtoMessage() {} +func (*RoleAssignment) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{3} +} +func (m *RoleAssignment) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RoleAssignment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RoleAssignment.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RoleAssignment) XXX_Merge(src proto.Message) { + xxx_messageInfo_RoleAssignment.Merge(m, src) +} +func (m *RoleAssignment) XXX_Size() int { + return m.Size() +} +func (m *RoleAssignment) XXX_DiscardUnknown() { + xxx_messageInfo_RoleAssignment.DiscardUnknown(m) +} + +var xxx_messageInfo_RoleAssignment proto.InternalMessageInfo + +type isRoleAssignment_RoleSelector interface { + isRoleAssignment_RoleSelector() + MarshalTo([]byte) (int, error) + Size() int +} + +type RoleAssignment_RegistryRole struct { + RegistryRole RegistryRole `protobuf:"varint,1,opt,name=registry_role,json=registryRole,proto3,enum=provenance.registry.v1.RegistryRole,oneof" json:"registry_role,omitempty"` +} +type RoleAssignment_NftRole struct { + NftRole NftRole `protobuf:"varint,2,opt,name=nft_role,json=nftRole,proto3,enum=provenance.registry.v1.NftRole,oneof" json:"nft_role,omitempty"` +} +type RoleAssignment_RolePriority struct { + RolePriority *RolePriority `protobuf:"bytes,3,opt,name=role_priority,json=rolePriority,proto3,oneof" json:"role_priority,omitempty"` +} + +func (*RoleAssignment_RegistryRole) isRoleAssignment_RoleSelector() {} +func (*RoleAssignment_NftRole) isRoleAssignment_RoleSelector() {} +func (*RoleAssignment_RolePriority) isRoleAssignment_RoleSelector() {} + +func (m *RoleAssignment) GetRoleSelector() isRoleAssignment_RoleSelector { + if m != nil { + return m.RoleSelector + } + return nil +} + +func (m *RoleAssignment) GetRegistryRole() RegistryRole { + if x, ok := m.GetRoleSelector().(*RoleAssignment_RegistryRole); ok { + return x.RegistryRole + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RoleAssignment) GetNftRole() NftRole { + if x, ok := m.GetRoleSelector().(*RoleAssignment_NftRole); ok { + return x.NftRole + } + return NftRole_NFT_ROLE_UNSPECIFIED +} + +func (m *RoleAssignment) GetRolePriority() *RolePriority { + if x, ok := m.GetRoleSelector().(*RoleAssignment_RolePriority); ok { + return x.RolePriority + } + return nil +} + +func (m *RoleAssignment) GetAssignment() Assignment { + if m != nil { + return m.Assignment + } + return Assignment_ASSIGNMENT_UNSPECIFIED +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*RoleAssignment) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*RoleAssignment_RegistryRole)(nil), + (*RoleAssignment_NftRole)(nil), + (*RoleAssignment_RolePriority)(nil), + } +} + +// RolePriority is an ordered list of role entries used for fallback/hierarchical authority. +// The keeper uses the first role in the list that exists. +type RolePriority struct { + // entries is the ordered list of roles to check. The first existing role is used. + Entries []*RolePriorityEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty"` +} + +func (m *RolePriority) Reset() { *m = RolePriority{} } +func (m *RolePriority) String() string { return proto.CompactTextString(m) } +func (*RolePriority) ProtoMessage() {} +func (*RolePriority) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{4} +} +func (m *RolePriority) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RolePriority) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RolePriority.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RolePriority) XXX_Merge(src proto.Message) { + xxx_messageInfo_RolePriority.Merge(m, src) +} +func (m *RolePriority) XXX_Size() int { + return m.Size() +} +func (m *RolePriority) XXX_DiscardUnknown() { + xxx_messageInfo_RolePriority.DiscardUnknown(m) +} + +var xxx_messageInfo_RolePriority proto.InternalMessageInfo + +func (m *RolePriority) GetEntries() []*RolePriorityEntry { + if m != nil { + return m.Entries + } + return nil +} + +// RolePriorityEntry is a single entry in a RolePriority list. +type RolePriorityEntry struct { + // role identifies the role in this priority slot. + // + // Types that are valid to be assigned to Role: + // + // *RolePriorityEntry_RegistryRole + // *RolePriorityEntry_NftRole + Role isRolePriorityEntry_Role `protobuf_oneof:"role"` +} + +func (m *RolePriorityEntry) Reset() { *m = RolePriorityEntry{} } +func (m *RolePriorityEntry) String() string { return proto.CompactTextString(m) } +func (*RolePriorityEntry) ProtoMessage() {} +func (*RolePriorityEntry) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{5} +} +func (m *RolePriorityEntry) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RolePriorityEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RolePriorityEntry.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RolePriorityEntry) XXX_Merge(src proto.Message) { + xxx_messageInfo_RolePriorityEntry.Merge(m, src) +} +func (m *RolePriorityEntry) XXX_Size() int { + return m.Size() +} +func (m *RolePriorityEntry) XXX_DiscardUnknown() { + xxx_messageInfo_RolePriorityEntry.DiscardUnknown(m) +} + +var xxx_messageInfo_RolePriorityEntry proto.InternalMessageInfo + +type isRolePriorityEntry_Role interface { + isRolePriorityEntry_Role() + MarshalTo([]byte) (int, error) + Size() int +} + +type RolePriorityEntry_RegistryRole struct { + RegistryRole RegistryRole `protobuf:"varint,1,opt,name=registry_role,json=registryRole,proto3,enum=provenance.registry.v1.RegistryRole,oneof" json:"registry_role,omitempty"` +} +type RolePriorityEntry_NftRole struct { + NftRole NftRole `protobuf:"varint,2,opt,name=nft_role,json=nftRole,proto3,enum=provenance.registry.v1.NftRole,oneof" json:"nft_role,omitempty"` +} + +func (*RolePriorityEntry_RegistryRole) isRolePriorityEntry_Role() {} +func (*RolePriorityEntry_NftRole) isRolePriorityEntry_Role() {} + +func (m *RolePriorityEntry) GetRole() isRolePriorityEntry_Role { + if m != nil { + return m.Role + } + return nil +} + +func (m *RolePriorityEntry) GetRegistryRole() RegistryRole { + if x, ok := m.GetRole().(*RolePriorityEntry_RegistryRole); ok { + return x.RegistryRole + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RolePriorityEntry) GetNftRole() NftRole { + if x, ok := m.GetRole().(*RolePriorityEntry_NftRole); ok { + return x.NftRole + } + return NftRole_NFT_ROLE_UNSPECIFIED +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*RolePriorityEntry) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*RolePriorityEntry_RegistryRole)(nil), + (*RolePriorityEntry_NftRole)(nil), + } +} + +func init() { + proto.RegisterEnum("provenance.registry.v1.SignatureType", SignatureType_name, SignatureType_value) + proto.RegisterEnum("provenance.registry.v1.NftRole", NftRole_name, NftRole_value) + proto.RegisterEnum("provenance.registry.v1.Assignment", Assignment_name, Assignment_value) + proto.RegisterType((*RoleAuthorization)(nil), "provenance.registry.v1.RoleAuthorization") + proto.RegisterType((*Authorization)(nil), "provenance.registry.v1.Authorization") + proto.RegisterType((*SignatureRequirement)(nil), "provenance.registry.v1.SignatureRequirement") + proto.RegisterType((*RoleAssignment)(nil), "provenance.registry.v1.RoleAssignment") + proto.RegisterType((*RolePriority)(nil), "provenance.registry.v1.RolePriority") + proto.RegisterType((*RolePriorityEntry)(nil), "provenance.registry.v1.RolePriorityEntry") +} + +func init() { + proto.RegisterFile("provenance/registry/v1/authorization.proto", fileDescriptor_3c5d2a6c632d9975) +} + +var fileDescriptor_3c5d2a6c632d9975 = []byte{ + // 648 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x55, 0xcf, 0x4e, 0x13, 0x41, + 0x18, 0xef, 0x94, 0x02, 0xfa, 0x01, 0x75, 0x9d, 0x10, 0x52, 0x31, 0x59, 0x9a, 0x8d, 0x10, 0x6c, + 0xb4, 0x0d, 0xe8, 0x41, 0x23, 0x97, 0x16, 0x06, 0x6d, 0x2c, 0x4b, 0x9d, 0x6e, 0x43, 0xf0, 0xb2, + 0x29, 0x75, 0x80, 0x8d, 0x65, 0xa7, 0xce, 0x4e, 0x89, 0xf5, 0xe2, 0x23, 0xe8, 0x03, 0x78, 0xf6, + 0x29, 0xf4, 0xe0, 0xcd, 0x23, 0x47, 0x8f, 0x06, 0x5e, 0xc4, 0xec, 0x74, 0x97, 0xee, 0x56, 0xb6, + 0xea, 0xcd, 0xdb, 0xcc, 0x7c, 0xbf, 0x7f, 0xf3, 0xcd, 0xcc, 0x2e, 0x14, 0xba, 0x82, 0x9f, 0x32, + 0xb7, 0xe5, 0xb6, 0x59, 0x49, 0xb0, 0x23, 0xc7, 0x93, 0xa2, 0x5f, 0x3a, 0x5d, 0x2b, 0xb5, 0x7a, + 0xf2, 0x98, 0x0b, 0xe7, 0x5d, 0x4b, 0x3a, 0xdc, 0x2d, 0x76, 0x05, 0x97, 0x1c, 0x2f, 0x0c, 0xb1, + 0xc5, 0x10, 0x5b, 0x3c, 0x5d, 0x5b, 0x5c, 0x4e, 0xd0, 0xb8, 0xc4, 0x28, 0xba, 0xf1, 0x09, 0xc1, + 0x4d, 0xca, 0x3b, 0xac, 0x1c, 0x95, 0xc6, 0x8f, 0x20, 0x23, 0x78, 0x87, 0xe5, 0x50, 0x1e, 0xad, + 0x66, 0xd7, 0xef, 0x14, 0xaf, 0xf6, 0x28, 0xd2, 0x60, 0xec, 0x0b, 0x50, 0xc5, 0xc0, 0x3b, 0x90, + 0x8d, 0xa5, 0xf4, 0x72, 0xe9, 0xfc, 0xc4, 0xea, 0xcc, 0xfa, 0x72, 0x92, 0x46, 0xcc, 0x98, 0x8e, + 0x90, 0x8d, 0xf7, 0x30, 0x17, 0x4f, 0x96, 0x87, 0x99, 0x57, 0xcc, 0x6b, 0x0b, 0xa7, 0xeb, 0x4f, + 0x55, 0xc0, 0xeb, 0x34, 0xba, 0x84, 0x6b, 0x00, 0x9e, 0x73, 0xe4, 0xb6, 0x64, 0x4f, 0xb0, 0xd0, + 0xfd, 0x5e, 0x92, 0x7b, 0x23, 0x44, 0x52, 0xf6, 0xa6, 0xe7, 0x08, 0x76, 0xc2, 0x5c, 0x49, 0x23, + 0x7c, 0xe3, 0x03, 0x82, 0xf9, 0xab, 0x40, 0xf8, 0x31, 0x64, 0x64, 0xbf, 0x1b, 0xb6, 0x68, 0xf9, + 0x8f, 0x06, 0x56, 0xbf, 0xcb, 0xa8, 0xa2, 0xe0, 0x0d, 0x98, 0xf4, 0x7b, 0x15, 0x86, 0x5b, 0x49, + 0x6c, 0xaf, 0x7f, 0x2e, 0x9e, 0x1f, 0x46, 0xc5, 0x1a, 0x90, 0x8c, 0xaf, 0x69, 0xc8, 0xc6, 0x2b, + 0xf8, 0x39, 0xcc, 0x85, 0x3c, 0xfb, 0x5f, 0xcf, 0xed, 0x59, 0x8a, 0xce, 0x8a, 0xc8, 0x1c, 0x6f, + 0xc0, 0x35, 0xf7, 0x50, 0x0e, 0x74, 0xd2, 0x4a, 0x67, 0x29, 0x49, 0xc7, 0x3c, 0x94, 0x81, 0xc4, + 0xb4, 0x3b, 0x18, 0xaa, 0x28, 0xbc, 0xc3, 0xec, 0xae, 0x70, 0xb8, 0x70, 0x64, 0x3f, 0x37, 0x91, + 0x47, 0xab, 0x33, 0x63, 0xa2, 0xf0, 0x0e, 0xab, 0x07, 0x58, 0x15, 0x25, 0x32, 0xc7, 0x15, 0x80, + 0xd6, 0xe5, 0x2e, 0x73, 0x19, 0x15, 0xc6, 0x48, 0xbc, 0x48, 0xc3, 0x4e, 0x45, 0x58, 0x95, 0x1b, + 0x41, 0x20, 0x8f, 0x75, 0x58, 0x5b, 0x72, 0x61, 0x34, 0x60, 0x36, 0x6a, 0x8a, 0x37, 0x61, 0x9a, + 0xb9, 0x52, 0x38, 0xcc, 0xcb, 0x21, 0x75, 0x1e, 0x77, 0xff, 0x26, 0x2b, 0x71, 0xfd, 0x7e, 0x85, + 0x4c, 0xe3, 0x73, 0xf0, 0x8c, 0x62, 0xe5, 0xff, 0xe8, 0x5c, 0x2a, 0x53, 0x83, 0x17, 0x5d, 0xf8, + 0x86, 0x60, 0x2e, 0x76, 0x27, 0xb1, 0x0e, 0x8b, 0x8d, 0xea, 0x53, 0xb3, 0x6c, 0x35, 0x29, 0xb1, + 0xad, 0xfd, 0x3a, 0xb1, 0x9b, 0x66, 0xa3, 0x4e, 0x36, 0xab, 0xdb, 0x55, 0xb2, 0xa5, 0xa5, 0xf0, + 0x12, 0xdc, 0x1e, 0xa9, 0x53, 0xf2, 0xa2, 0x59, 0xa5, 0x64, 0xcb, 0x2e, 0xd7, 0x6a, 0x1a, 0xc2, + 0x2b, 0x60, 0x8c, 0x01, 0xd8, 0xd5, 0x6d, 0xbb, 0x41, 0x2c, 0x2d, 0x3d, 0x56, 0xc8, 0xdc, 0xd7, + 0x26, 0xc6, 0x0a, 0x99, 0xfb, 0xa1, 0x50, 0xa6, 0xf0, 0x04, 0xa6, 0x83, 0x1d, 0xe2, 0x1c, 0xcc, + 0x9b, 0xdb, 0x96, 0x4d, 0x77, 0x6b, 0xa3, 0xb1, 0x17, 0x00, 0x5f, 0x56, 0xfc, 0xc1, 0xee, 0x9e, + 0x49, 0xa8, 0x86, 0x0a, 0x5f, 0x10, 0x40, 0xe4, 0xe9, 0x2c, 0xc2, 0x42, 0xb9, 0xe1, 0xbb, 0xee, + 0x10, 0xd3, 0xfa, 0x5d, 0x22, 0x52, 0xdb, 0x6c, 0x52, 0x4a, 0x4c, 0x4b, 0x43, 0x23, 0x9c, 0x60, + 0x5d, 0x35, 0x23, 0x9d, 0x54, 0x53, 0xfb, 0xc3, 0x90, 0x8d, 0xd4, 0x4c, 0xb2, 0xa7, 0x65, 0x46, + 0x3c, 0x4c, 0xb2, 0xa7, 0xb0, 0x93, 0x57, 0xad, 0xd7, 0x6a, 0xda, 0x54, 0xe5, 0xf5, 0xf7, 0x73, + 0x1d, 0x9d, 0x9d, 0xeb, 0xe8, 0xe7, 0xb9, 0x8e, 0x3e, 0x5e, 0xe8, 0xa9, 0xb3, 0x0b, 0x3d, 0xf5, + 0xe3, 0x42, 0x4f, 0xc1, 0x2d, 0x87, 0x27, 0xdc, 0x87, 0x3a, 0x7a, 0xf9, 0xf0, 0xc8, 0x91, 0xc7, + 0xbd, 0x83, 0x62, 0x9b, 0x9f, 0x94, 0x86, 0xa0, 0xfb, 0x0e, 0x8f, 0xcc, 0x4a, 0x6f, 0x87, 0x3f, + 0x0a, 0xff, 0x3b, 0xe5, 0x1d, 0x4c, 0xa9, 0x7f, 0xc4, 0x83, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x56, 0x21, 0xec, 0x03, 0x90, 0x06, 0x00, 0x00, +} + +func (m *RoleAuthorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleAuthorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAuthorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Authorizations) > 0 { + for iNdEx := len(m.Authorizations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Authorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.Role != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.Role)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Authorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Authorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Authorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signatures) > 0 { + for iNdEx := len(m.Signatures) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Signatures[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Description) > 0 { + i -= len(m.Description) + copy(dAtA[i:], m.Description) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.Description))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *SignatureRequirement) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SignatureRequirement) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SignatureRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Roles) > 0 { + for iNdEx := len(m.Roles) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Roles[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.Type != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.Type)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *RoleAssignment) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleAssignment) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAssignment) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Assignment != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.Assignment)) + i-- + dAtA[i] = 0x20 + } + if m.RoleSelector != nil { + { + size := m.RoleSelector.Size() + i -= size + if _, err := m.RoleSelector.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *RoleAssignment_RegistryRole) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAssignment_RegistryRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.RegistryRole)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} +func (m *RoleAssignment_NftRole) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAssignment_NftRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.NftRole)) + i-- + dAtA[i] = 0x10 + return len(dAtA) - i, nil +} +func (m *RoleAssignment_RolePriority) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleAssignment_RolePriority) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.RolePriority != nil { + { + size, err := m.RolePriority.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + return len(dAtA) - i, nil +} +func (m *RolePriority) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RolePriority) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RolePriority) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Entries) > 0 { + for iNdEx := len(m.Entries) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Entries[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *RolePriorityEntry) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RolePriorityEntry) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RolePriorityEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Role != nil { + { + size := m.Role.Size() + i -= size + if _, err := m.Role.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *RolePriorityEntry_RegistryRole) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RolePriorityEntry_RegistryRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.RegistryRole)) + i-- + dAtA[i] = 0x8 + return len(dAtA) - i, nil +} +func (m *RolePriorityEntry_NftRole) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RolePriorityEntry_NftRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + i = encodeVarintAuthorization(dAtA, i, uint64(m.NftRole)) + i-- + dAtA[i] = 0x10 + return len(dAtA) - i, nil +} +func encodeVarintAuthorization(dAtA []byte, offset int, v uint64) int { + offset -= sovAuthorization(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *RoleAuthorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Role != 0 { + n += 1 + sovAuthorization(uint64(m.Role)) + } + if len(m.Authorizations) > 0 { + for _, e := range m.Authorizations { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + return n +} + +func (m *Authorization) Size() (n int) { + if m == nil { + return 0 + } + var l int _ = l l = len(m.Description) if l > 0 { n += 1 + l + sovAuthorization(uint64(l)) } - for _, e := range m.Signatures { - l = e.Size() + if len(m.Signatures) > 0 { + for _, e := range m.Signatures { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + return n +} + +func (m *SignatureRequirement) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Type != 0 { + n += 1 + sovAuthorization(uint64(m.Type)) + } + if len(m.Roles) > 0 { + for _, e := range m.Roles { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + return n +} + +func (m *RoleAssignment) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.RoleSelector != nil { + n += m.RoleSelector.Size() + } + if m.Assignment != 0 { + n += 1 + sovAuthorization(uint64(m.Assignment)) + } + return n +} + +func (m *RoleAssignment_RegistryRole) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovAuthorization(uint64(m.RegistryRole)) + return n +} +func (m *RoleAssignment_NftRole) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovAuthorization(uint64(m.NftRole)) + return n +} +func (m *RoleAssignment_RolePriority) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.RolePriority != nil { + l = m.RolePriority.Size() n += 1 + l + sovAuthorization(uint64(l)) } return n } +func (m *RolePriority) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Entries) > 0 { + for _, e := range m.Entries { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + return n +} -func (m *Authorization) Unmarshal(dAtA []byte) error { +func (m *RolePriorityEntry) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Role != nil { + n += m.Role.Size() + } + return n +} + +func (m *RolePriorityEntry_RegistryRole) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovAuthorization(uint64(m.RegistryRole)) + return n +} +func (m *RolePriorityEntry_NftRole) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + n += 1 + sovAuthorization(uint64(m.NftRole)) + return n +} + +func sovAuthorization(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozAuthorization(x uint64) (n int) { + return sovAuthorization(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RoleAuthorization) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -446,17 +1116,17 @@ func (m *Authorization) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Authorization: wiretype end group for non-group") + return fmt.Errorf("proto: RoleAuthorization: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Authorization: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: RoleAuthorization: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) } - var stringLen uint64 + m.Role = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowAuthorization @@ -466,27 +1136,14 @@ func (m *Authorization) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.Role |= RegistryRole(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthAuthorization - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthAuthorization - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Description = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signatures", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Authorizations", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -513,8 +1170,8 @@ func (m *Authorization) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signatures = append(m.Signatures, SignatureRequirement{}) - if err := m.Signatures[len(m.Signatures)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Authorizations = append(m.Authorizations, &Authorization{}) + if err := m.Authorizations[len(m.Authorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -527,124 +1184,19 @@ func (m *Authorization) Unmarshal(dAtA []byte) error { if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLengthAuthorization } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} - -// ----------------------------------------------------------------------- -// SignatureRequirement -// ----------------------------------------------------------------------- - -// SignatureRequirement defines a single signature check within an authorization path. -type SignatureRequirement struct { - Type SignatureType `protobuf:"varint,1,opt,name=type,proto3,enum=provenance.registry.v1.SignatureType" json:"type,omitempty"` - Roles []RoleAssignment `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles"` -} - -func (m *SignatureRequirement) Reset() { *m = SignatureRequirement{} } -func (m *SignatureRequirement) String() string { return proto.CompactTextString(m) } -func (*SignatureRequirement) ProtoMessage() {} -func (m *SignatureRequirement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *SignatureRequirement) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_SignatureRequirement.Marshal(b, m, deterministic) - } - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil -} -func (m *SignatureRequirement) XXX_Merge(src proto.Message) { - xxx_messageInfo_SignatureRequirement.Merge(m, src) -} -func (m *SignatureRequirement) XXX_Size() int { return m.Size() } -func (m *SignatureRequirement) XXX_DiscardUnknown() { - xxx_messageInfo_SignatureRequirement.DiscardUnknown(m) -} - -var xxx_messageInfo_SignatureRequirement proto.InternalMessageInfo - -func (m *SignatureRequirement) GetType() SignatureType { - if m != nil { - return m.Type - } - return SignatureType_SIGNATURE_TYPE_UNSPECIFIED -} - -func (m *SignatureRequirement) GetRoles() []RoleAssignment { - if m != nil { - return m.Roles - } - return nil -} - -func (m *SignatureRequirement) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *SignatureRequirement) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *SignatureRequirement) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Roles) > 0 { - for iNdEx := len(m.Roles) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Roles[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthorization(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0x12 + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy } } - if m.Type != 0 { - i = encodeVarintAuthorization(dAtA, i, uint64(m.Type)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} -func (m *SignatureRequirement) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Type != 0 { - n += 1 + sovAuthorization(uint64(m.Type)) - } - for _, e := range m.Roles { - l = e.Size() - n += 1 + l + sovAuthorization(uint64(l)) + if iNdEx > l { + return io.ErrUnexpectedEOF } - return n + return nil } - -func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { +func (m *Authorization) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -667,17 +1219,17 @@ func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SignatureRequirement: wiretype end group for non-group") + return fmt.Errorf("proto: Authorization: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SignatureRequirement: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Authorization: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) } - m.Type = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowAuthorization @@ -687,14 +1239,27 @@ func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Type |= SignatureType(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signatures", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -721,8 +1286,8 @@ func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Roles = append(m.Roles, RoleAssignment{}) - if err := m.Roles[len(m.Roles)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Signatures = append(m.Signatures, &SignatureRequirement{}) + if err := m.Signatures[len(m.Signatures)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -741,203 +1306,115 @@ func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { iNdEx += skippy } } + if iNdEx > l { return io.ErrUnexpectedEOF } return nil } - -// ----------------------------------------------------------------------- -// RoleAssignment -// ----------------------------------------------------------------------- - -// RoleAssignment_RoleSelector is the oneof interface for RoleAssignment. -type isRoleAssignment_RoleSelector interface { - isRoleAssignment_RoleSelector() - MarshalTo([]byte) (int, error) - Size() int -} - -type RoleAssignment_RegistryRole struct { - RegistryRole RegistryRole `protobuf:"varint,1,opt,name=registry_role,json=registryRole,proto3,enum=provenance.registry.v1.RegistryRole,oneof"` -} - -type RoleAssignment_NftRole struct { - NftRole NftRole `protobuf:"varint,2,opt,name=nft_role,json=nftRole,proto3,enum=provenance.registry.v1.NftRole,oneof"` -} - -type RoleAssignment_RolePriority struct { - RolePriority *RolePriority `protobuf:"bytes,3,opt,name=role_priority,json=rolePriority,proto3,oneof"` -} - -func (*RoleAssignment_RegistryRole) isRoleAssignment_RoleSelector() {} -func (*RoleAssignment_NftRole) isRoleAssignment_RoleSelector() {} -func (*RoleAssignment_RolePriority) isRoleAssignment_RoleSelector() {} - -func (m *RoleAssignment_RegistryRole) MarshalTo(dAtA []byte) (int, error) { - i := len(dAtA) - i = encodeVarintAuthorization(dAtA, i, uint64(m.RegistryRole)) - i-- - dAtA[i] = 0x8 - return len(dAtA) - i, nil -} - -func (m *RoleAssignment_NftRole) MarshalTo(dAtA []byte) (int, error) { - i := len(dAtA) - i = encodeVarintAuthorization(dAtA, i, uint64(m.NftRole)) - i-- - dAtA[i] = 0x10 - return len(dAtA) - i, nil -} - -func (m *RoleAssignment_RolePriority) MarshalTo(dAtA []byte) (int, error) { - i := len(dAtA) - if m.RolePriority != nil { - size, err := m.RolePriority.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err +func (m *SignatureRequirement) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } } - i -= size - i = encodeVarintAuthorization(dAtA, i, uint64(size)) - } - i-- - dAtA[i] = 0x1a - return len(dAtA) - i, nil -} - -func (m *RoleAssignment_RegistryRole) Size() int { - return 1 + sovAuthorization(uint64(m.RegistryRole)) -} - -func (m *RoleAssignment_NftRole) Size() int { - return 1 + sovAuthorization(uint64(m.NftRole)) -} - -func (m *RoleAssignment_RolePriority) Size() int { - if m.RolePriority == nil { - return 1 + sovAuthorization(0) - } - s := m.RolePriority.Size() - return 1 + s + sovAuthorization(uint64(s)) -} - -// RoleAssignment specifies which role and assignment type to resolve for a signature check. -type RoleAssignment struct { - // Types that are valid to be assigned to RoleSelector: - // *RoleAssignment_RegistryRole - // *RoleAssignment_NftRole - // *RoleAssignment_RolePriority - RoleSelector isRoleAssignment_RoleSelector `protobuf_oneof:"role_selector"` - // assignment specifies which address(es) to resolve for the role. - Assignment Assignment `protobuf:"varint,4,opt,name=assignment,proto3,enum=provenance.registry.v1.Assignment" json:"assignment,omitempty"` -} - -func (m *RoleAssignment) Reset() { *m = RoleAssignment{} } -func (m *RoleAssignment) String() string { return proto.CompactTextString(m) } -func (*RoleAssignment) ProtoMessage() {} -func (m *RoleAssignment) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *RoleAssignment) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RoleAssignment.Marshal(b, m, deterministic) - } - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil -} -func (m *RoleAssignment) XXX_Merge(src proto.Message) { xxx_messageInfo_RoleAssignment.Merge(m, src) } -func (m *RoleAssignment) XXX_Size() int { return m.Size() } -func (m *RoleAssignment) XXX_DiscardUnknown() { xxx_messageInfo_RoleAssignment.DiscardUnknown(m) } - -var xxx_messageInfo_RoleAssignment proto.InternalMessageInfo - -func (m *RoleAssignment) GetRoleSelector() isRoleAssignment_RoleSelector { - if m != nil { - return m.RoleSelector - } - return nil -} - -func (m *RoleAssignment) GetRegistryRole() RegistryRole { - if x, ok := m.GetRoleSelector().(*RoleAssignment_RegistryRole); ok { - return x.RegistryRole - } - return RegistryRole_REGISTRY_ROLE_UNSPECIFIED -} - -func (m *RoleAssignment) GetNftRole() NftRole { - if x, ok := m.GetRoleSelector().(*RoleAssignment_NftRole); ok { - return x.NftRole - } - return NftRole_NFT_ROLE_UNSPECIFIED -} - -func (m *RoleAssignment) GetRolePriority() *RolePriority { - if x, ok := m.GetRoleSelector().(*RoleAssignment_RolePriority); ok { - return x.RolePriority - } - return nil -} - -func (m *RoleAssignment) GetAssignment() Assignment { - if m != nil { - return m.Assignment - } - return Assignment_ASSIGNMENT_UNSPECIFIED -} - -func (m *RoleAssignment) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RoleAssignment) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RoleAssignment) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Assignment != 0 { - i = encodeVarintAuthorization(dAtA, i, uint64(m.Assignment)) - i-- - dAtA[i] = 0x20 - } - if m.RoleSelector != nil { - n, err := m.RoleSelector.MarshalTo(dAtA[:i]) - if err != nil { - return 0, err + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SignatureRequirement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SignatureRequirement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= SignatureType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Roles = append(m.Roles, &RoleAssignment{}) + if err := m.Roles[len(m.Roles)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy } - i -= n } - return len(dAtA) - i, nil -} -func (m *RoleAssignment) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.RoleSelector != nil { - n += m.RoleSelector.Size() - } - if m.Assignment != 0 { - n += 1 + sovAuthorization(uint64(m.Assignment)) + if iNdEx > l { + return io.ErrUnexpectedEOF } - return n + return nil } - func (m *RoleAssignment) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -986,7 +1463,7 @@ func (m *RoleAssignment) Unmarshal(dAtA []byte) error { break } } - m.RoleSelector = &RoleAssignment_RegistryRole{RegistryRole: v} + m.RoleSelector = &RoleAssignment_RegistryRole{v} case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field NftRole", wireType) @@ -1006,7 +1483,7 @@ func (m *RoleAssignment) Unmarshal(dAtA []byte) error { break } } - m.RoleSelector = &RoleAssignment_NftRole{NftRole: v} + m.RoleSelector = &RoleAssignment_NftRole{v} case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field RolePriority", wireType) @@ -1040,7 +1517,7 @@ func (m *RoleAssignment) Unmarshal(dAtA []byte) error { if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } - m.RoleSelector = &RoleAssignment_RolePriority{RolePriority: v} + m.RoleSelector = &RoleAssignment_RolePriority{v} iNdEx = postIndex case 4: if wireType != 0 { @@ -1076,95 +1553,12 @@ func (m *RoleAssignment) Unmarshal(dAtA []byte) error { iNdEx += skippy } } + if iNdEx > l { return io.ErrUnexpectedEOF } return nil } - -// ----------------------------------------------------------------------- -// RolePriority -// ----------------------------------------------------------------------- - -// RolePriority is an ordered list of role entries; the first existing role is used. -type RolePriority struct { - Entries []RolePriorityEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries"` -} - -func (m *RolePriority) Reset() { *m = RolePriority{} } -func (m *RolePriority) String() string { return proto.CompactTextString(m) } -func (*RolePriority) ProtoMessage() {} -func (m *RolePriority) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *RolePriority) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RolePriority.Marshal(b, m, deterministic) - } - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil -} -func (m *RolePriority) XXX_Merge(src proto.Message) { xxx_messageInfo_RolePriority.Merge(m, src) } -func (m *RolePriority) XXX_Size() int { return m.Size() } -func (m *RolePriority) XXX_DiscardUnknown() { xxx_messageInfo_RolePriority.DiscardUnknown(m) } - -var xxx_messageInfo_RolePriority proto.InternalMessageInfo - -func (m *RolePriority) GetEntries() []RolePriorityEntry { - if m != nil { - return m.Entries - } - return nil -} - -func (m *RolePriority) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RolePriority) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RolePriority) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - for iNdEx := len(m.Entries) - 1; iNdEx >= 0; iNdEx-- { - size, err := m.Entries[iNdEx].MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarintAuthorization(dAtA, i, uint64(size)) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - -func (m *RolePriority) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - for _, e := range m.Entries { - l = e.Size() - n += 1 + l + sovAuthorization(uint64(l)) - } - return n -} - func (m *RolePriority) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1223,7 +1617,7 @@ func (m *RolePriority) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Entries = append(m.Entries, RolePriorityEntry{}) + m.Entries = append(m.Entries, &RolePriorityEntry{}) if err := m.Entries[len(m.Entries)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1243,153 +1637,12 @@ func (m *RolePriority) Unmarshal(dAtA []byte) error { iNdEx += skippy } } + if iNdEx > l { return io.ErrUnexpectedEOF } return nil } - -// ----------------------------------------------------------------------- -// RolePriorityEntry -// ----------------------------------------------------------------------- - -type isRolePriorityEntry_Role interface { - isRolePriorityEntry_Role() - MarshalTo([]byte) (int, error) - Size() int -} - -type RolePriorityEntry_RegistryRole struct { - RegistryRole RegistryRole `protobuf:"varint,1,opt,name=registry_role,json=registryRole,proto3,enum=provenance.registry.v1.RegistryRole,oneof"` -} - -type RolePriorityEntry_NftRole struct { - NftRole NftRole `protobuf:"varint,2,opt,name=nft_role,json=nftRole,proto3,enum=provenance.registry.v1.NftRole,oneof"` -} - -func (*RolePriorityEntry_RegistryRole) isRolePriorityEntry_Role() {} -func (*RolePriorityEntry_NftRole) isRolePriorityEntry_Role() {} - -func (m *RolePriorityEntry_RegistryRole) MarshalTo(dAtA []byte) (int, error) { - i := len(dAtA) - i = encodeVarintAuthorization(dAtA, i, uint64(m.RegistryRole)) - i-- - dAtA[i] = 0x8 - return len(dAtA) - i, nil -} - -func (m *RolePriorityEntry_NftRole) MarshalTo(dAtA []byte) (int, error) { - i := len(dAtA) - i = encodeVarintAuthorization(dAtA, i, uint64(m.NftRole)) - i-- - dAtA[i] = 0x10 - return len(dAtA) - i, nil -} - -func (m *RolePriorityEntry_RegistryRole) Size() int { - return 1 + sovAuthorization(uint64(m.RegistryRole)) -} - -func (m *RolePriorityEntry_NftRole) Size() int { - return 1 + sovAuthorization(uint64(m.NftRole)) -} - -// RolePriorityEntry is a single role in a RolePriority list. -type RolePriorityEntry struct { - // Types that are valid to be assigned to Role: - // *RolePriorityEntry_RegistryRole - // *RolePriorityEntry_NftRole - Role isRolePriorityEntry_Role `protobuf_oneof:"role"` -} - -func (m *RolePriorityEntry) Reset() { *m = RolePriorityEntry{} } -func (m *RolePriorityEntry) String() string { return proto.CompactTextString(m) } -func (*RolePriorityEntry) ProtoMessage() {} -func (m *RolePriorityEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *RolePriorityEntry) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RolePriorityEntry.Marshal(b, m, deterministic) - } - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil -} -func (m *RolePriorityEntry) XXX_Merge(src proto.Message) { - xxx_messageInfo_RolePriorityEntry.Merge(m, src) -} -func (m *RolePriorityEntry) XXX_Size() int { return m.Size() } -func (m *RolePriorityEntry) XXX_DiscardUnknown() { - xxx_messageInfo_RolePriorityEntry.DiscardUnknown(m) -} - -var xxx_messageInfo_RolePriorityEntry proto.InternalMessageInfo - -func (m *RolePriorityEntry) GetRole() isRolePriorityEntry_Role { - if m != nil { - return m.Role - } - return nil -} - -func (m *RolePriorityEntry) GetRegistryRole() RegistryRole { - if x, ok := m.GetRole().(*RolePriorityEntry_RegistryRole); ok { - return x.RegistryRole - } - return RegistryRole_REGISTRY_ROLE_UNSPECIFIED -} - -func (m *RolePriorityEntry) GetNftRole() NftRole { - if x, ok := m.GetRole().(*RolePriorityEntry_NftRole); ok { - return x.NftRole - } - return NftRole_NFT_ROLE_UNSPECIFIED -} - -func (m *RolePriorityEntry) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RolePriorityEntry) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RolePriorityEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if m.Role != nil { - n, err := m.Role.MarshalTo(dAtA[:i]) - if err != nil { - return 0, err - } - i -= n - } - return len(dAtA) - i, nil -} - -func (m *RolePriorityEntry) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Role != nil { - n += m.Role.Size() - } - return n -} - func (m *RolePriorityEntry) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1438,7 +1691,7 @@ func (m *RolePriorityEntry) Unmarshal(dAtA []byte) error { break } } - m.Role = &RolePriorityEntry_RegistryRole{RegistryRole: v} + m.Role = &RolePriorityEntry_RegistryRole{v} case 2: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field NftRole", wireType) @@ -1458,7 +1711,7 @@ func (m *RolePriorityEntry) Unmarshal(dAtA []byte) error { break } } - m.Role = &RolePriorityEntry_NftRole{NftRole: v} + m.Role = &RolePriorityEntry_NftRole{v} default: iNdEx = preIndex skippy, err := skipAuthorization(dAtA[iNdEx:]) @@ -1474,32 +1727,12 @@ func (m *RolePriorityEntry) Unmarshal(dAtA []byte) error { iNdEx += skippy } } + if iNdEx > l { return io.ErrUnexpectedEOF } return nil } - -// ----------------------------------------------------------------------- -// Helpers -// ----------------------------------------------------------------------- - -func encodeVarintAuthorization(dAtA []byte, offset int, v uint64) int { - offset -= sovAuthorization(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} - -func sovAuthorization(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} - func skipAuthorization(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From c820f073b938b2ae033d08236c45644d7aa870ff Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 18 Jun 2026 14:25:52 -0600 Subject: [PATCH 03/36] Add Option B single-signer role-change approval accumulation Implements multi-party Controller authorization using single-signer messages whose approvals accumulate in registry state and auto-apply once the role's policy is satisfied. Unlike the native repeated-signers model, every message is single-signer, so each approval is delegable via authz. - proto: RoleChangeOperation enum + PendingRoleChange state; MsgProposeRoleChange and MsgApproveRoleChange (single-signer); proposed/approved/applied events. - keeper: PendingRoleChanges collection + accumulation/auto-apply reusing the existing role-typed policy engine. - types: deterministic change-id helper, ValidateBasic, event constructors. - tests: full Controller matrix plus authz delegation proof. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/events.proto | 25 + proto/provenance/registry/v1/registry.proto | 37 + proto/provenance/registry/v1/tx.proto | 60 +- x/registry/keeper/keeper.go | 15 +- x/registry/keeper/keys.go | 3 +- x/registry/keeper/msg_server.go | 20 + x/registry/keeper/pending.go | 157 +++ ...ole_change_accumulation_acceptance_test.go | 294 ++++ x/registry/types/authorization.pb.go | 1 - x/registry/types/errors.go | 6 + x/registry/types/events.go | 31 + x/registry/types/events.pb.go | 1130 ++++++++++++++- x/registry/types/msgs.go | 42 + x/registry/types/msgs_test.go | 61 + x/registry/types/pending.go | 38 + x/registry/types/registry.pb.go | 584 +++++++- x/registry/types/tx.pb.go | 1249 +++++++++++++++-- 17 files changed, 3565 insertions(+), 188 deletions(-) create mode 100644 x/registry/keeper/pending.go create mode 100644 x/registry/keeper/role_change_accumulation_acceptance_test.go create mode 100644 x/registry/types/pending.go diff --git a/proto/provenance/registry/v1/events.proto b/proto/provenance/registry/v1/events.proto index 0669c2d07b..2482dc3e2a 100644 --- a/proto/provenance/registry/v1/events.proto +++ b/proto/provenance/registry/v1/events.proto @@ -38,3 +38,28 @@ message EventRegistryBulkUpdated { string nft_id = 1; string asset_class_id = 2; } + +// EventRoleChangeProposed is emitted when a pending role change is opened. +message EventRoleChangeProposed { + string nft_id = 1; + string asset_class_id = 2; + string change_id = 3; + string role = 4; + string operation = 5; + string proposer = 6; +} + +// EventRoleChangeApproved is emitted when a party records an approval for a pending role change. +message EventRoleChangeApproved { + string change_id = 1; + string approver = 2; +} + +// EventRoleChangeApplied is emitted when a pending role change accumulates enough approvals and is applied. +message EventRoleChangeApplied { + string nft_id = 1; + string asset_class_id = 2; + string change_id = 3; + string role = 4; + string operation = 5; +} diff --git a/proto/provenance/registry/v1/registry.proto b/proto/provenance/registry/v1/registry.proto index 6bee34bba4..1dd8001221 100644 --- a/proto/provenance/registry/v1/registry.proto +++ b/proto/provenance/registry/v1/registry.proto @@ -83,4 +83,41 @@ message RolesEntry { // addresses is the list of blockchain addresses that can perform this role. // These addresses have the permissions associated with the specified role. repeated string addresses = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// RoleChangeOperation identifies the kind of role change a pending change applies. +enum RoleChangeOperation { + // ROLE_CHANGE_OPERATION_UNSPECIFIED is the default, invalid operation. + ROLE_CHANGE_OPERATION_UNSPECIFIED = 0; + // ROLE_CHANGE_OPERATION_GRANT adds the target addresses to the role when applied. + ROLE_CHANGE_OPERATION_GRANT = 1; + // ROLE_CHANGE_OPERATION_REVOKE removes the target addresses from the role when applied. + ROLE_CHANGE_OPERATION_REVOKE = 2; +} + +// PendingRoleChange is a role change awaiting the approvals required by the role's authorization +// policy. Each required party submits a single-signer approval; once the accumulated approvers +// satisfy the policy, the change is applied and this record is removed. +message PendingRoleChange { + // id is the deterministic identifier of this pending change + // (a hash of key + role + operation + sorted addresses). + string id = 1; + + // key identifies the registry entry the change applies to. + RegistryKey key = 2; + + // role is the role being changed. + RegistryRole role = 3; + + // operation is the kind of change to apply (grant or revoke) once approved. + RoleChangeOperation operation = 4; + + // addresses is the set of addresses the operation applies to. + repeated string addresses = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // proposer is the address that opened the pending change. + string proposer = 6 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // approvals is the accumulated set of addresses that have approved this change. + repeated string approvals = 7 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } \ No newline at end of file diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index da7a894685..9581ca73fe 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -42,6 +42,14 @@ service Msg { // SetRoles atomically sets the desired state for one or more roles on a registry entry. // Multiple signers are supported for multi-party authorization workflows. rpc SetRoles(MsgSetRoles) returns (MsgSetRolesResponse); + + // ProposeRoleChange opens a pending role change that accumulates single-signer approvals. + // The change auto-applies once the role's authorization policy is satisfied. + rpc ProposeRoleChange(MsgProposeRoleChange) returns (MsgProposeRoleChangeResponse); + + // ApproveRoleChange records a single-signer approval for an open pending role change. + // When the accumulated approvals satisfy the role's policy, the change is applied automatically. + rpc ApproveRoleChange(MsgApproveRoleChange) returns (MsgApproveRoleChangeResponse); } // MsgRegisterNFT represents a message to register a new NFT in the registry. @@ -188,4 +196,54 @@ message RoleUpdate { } // MsgSetRolesResponse defines the response for SetRoles. -message MsgSetRolesResponse {} \ No newline at end of file +message MsgSetRolesResponse {} + +// MsgProposeRoleChange opens a pending role change that collects single-signer approvals until +// the role's authorization policy is satisfied. Using single-signer messages (rather than one +// multi-signer message) keeps every step compatible with authz delegation. +message MsgProposeRoleChange { + option (cosmos.msg.v1.signer) = "signer"; + + // signer is the address proposing the change. It is recorded as the first approval; it only + // counts toward the policy if the signer is itself a required party. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // key identifies the registry entry to change. + RegistryKey key = 2; + + // role is the role being changed. + RegistryRole role = 3; + + // operation is the kind of change to apply once approved (grant or revoke). + RoleChangeOperation operation = 4; + + // addresses is the set of addresses the operation applies to. + repeated string addresses = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"]; +} + +// MsgProposeRoleChangeResponse defines the response for ProposeRoleChange. +message MsgProposeRoleChangeResponse { + // change_id is the deterministic identifier of the pending change. It is also returned when the + // change auto-applied immediately (e.g. a single-party policy satisfied by the proposer). + string change_id = 1; + + // applied is true if the proposal alone satisfied the policy and the change was applied. + bool applied = 2; +} + +// MsgApproveRoleChange records a single-signer approval for an open pending role change. +message MsgApproveRoleChange { + option (cosmos.msg.v1.signer) = "signer"; + + // signer is the approving party. Single-signer, so it can be delegated via authz. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // change_id is the identifier of the pending change to approve. + string change_id = 2; +} + +// MsgApproveRoleChangeResponse defines the response for ApproveRoleChange. +message MsgApproveRoleChangeResponse { + // applied is true if this approval satisfied the policy and the change was applied. + bool applied = 1; +} \ No newline at end of file diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index c3fb48cd40..85cf83be1d 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -19,9 +19,10 @@ import ( // Keeper defines the registry keeper. type Keeper struct { - cdc codec.BinaryCodec - schema collections.Schema - Registry collections.Map[collections.Pair[string, string], types.RegistryEntry] + cdc codec.BinaryCodec + schema collections.Schema + Registry collections.Map[collections.Pair[string, string], types.RegistryEntry] + PendingRoleChanges collections.Map[string, types.PendingRoleChange] NFTKeeper NFTKeeper MetadataKeeper MetadataKeeper @@ -42,6 +43,14 @@ func NewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, nftKeep codec.CollValue[types.RegistryEntry](cdc), ), + PendingRoleChanges: collections.NewMap( + sb, + collections.NewPrefix(pendingRoleChangePrefix), + "pending_role_changes", + collections.StringKey, + codec.CollValue[types.PendingRoleChange](cdc), + ), + NFTKeeper: nftKeeper, MetadataKeeper: metaDataKeeper, } diff --git a/x/registry/keeper/keys.go b/x/registry/keeper/keys.go index 282fb67bed..63463827dd 100644 --- a/x/registry/keeper/keys.go +++ b/x/registry/keeper/keys.go @@ -1,5 +1,6 @@ package keeper var ( - registryPrefix = []byte{0x01} + registryPrefix = []byte{0x01} + pendingRoleChangePrefix = []byte{0x02} ) diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 65b1b7e082..5ac0487af8 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -155,6 +155,26 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types return &types.MsgSetRolesResponse{}, nil } +// ProposeRoleChange opens a pending role change that accumulates single-signer approvals and +// auto-applies once the role's authorization policy is satisfied. +func (k msgServer) ProposeRoleChange(ctx context.Context, msg *types.MsgProposeRoleChange) (*types.MsgProposeRoleChangeResponse, error) { + id, applied, err := k.Keeper.ProposeRoleChange(ctx, msg.Signer, msg.Key, msg.Role, msg.Operation, msg.Addresses) + if err != nil { + return nil, err + } + return &types.MsgProposeRoleChangeResponse{ChangeId: id, Applied: applied}, nil +} + +// ApproveRoleChange records a single-signer approval for a pending role change and auto-applies +// it once the role's authorization policy is satisfied. +func (k msgServer) ApproveRoleChange(ctx context.Context, msg *types.MsgApproveRoleChange) (*types.MsgApproveRoleChangeResponse, error) { + applied, err := k.Keeper.ApproveRoleChange(ctx, msg.Signer, msg.ChangeId) + if err != nil { + return nil, err + } + return &types.MsgApproveRoleChangeResponse{Applied: applied}, nil +} + // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry and associated data for the specified key. func (k msgServer) UnregisterNFT(ctx context.Context, msg *types.MsgUnregisterNFT) (*types.MsgUnregisterNFTResponse, error) { diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go new file mode 100644 index 0000000000..c117b43236 --- /dev/null +++ b/x/registry/keeper/pending.go @@ -0,0 +1,157 @@ +package keeper + +import ( + "context" + "errors" + "fmt" + "slices" + + "cosmossdk.io/collections" + + "github.com/provenance-io/provenance/x/registry/types" +) + +// GetPendingRoleChange returns the pending role change for the given id, or nil if none exists. +func (k Keeper) GetPendingRoleChange(ctx context.Context, id string) (*types.PendingRoleChange, error) { + change, err := k.PendingRoleChanges.Get(ctx, id) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return nil, nil + } + return nil, err + } + return &change, nil +} + +// SetPendingRoleChange stores a pending role change. +func (k Keeper) SetPendingRoleChange(ctx context.Context, change types.PendingRoleChange) error { + return k.PendingRoleChanges.Set(ctx, change.Id, change) +} + +// RemovePendingRoleChange deletes a pending role change by id. +func (k Keeper) RemovePendingRoleChange(ctx context.Context, id string) error { + return k.PendingRoleChanges.Remove(ctx, id) +} + +// ProposeRoleChange opens (or re-uses) a pending role change and records the proposer's approval. +// If the accumulated approvals already satisfy the role's policy, the change is applied +// immediately. Returns the change id and whether it was applied. +func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *types.RegistryKey, role types.RegistryRole, op types.RoleChangeOperation, addresses []string) (string, bool, error) { + entry, err := k.GetRegistry(ctx, key) + if err != nil { + return "", false, err + } + if entry == nil { + return "", false, types.NewErrCodeRegistryNotFound(key.String()) + } + + if _, ok := types.RoleAuthorizationMap()[role]; !ok { + return "", false, types.NewErrCodeUnauthorized( + fmt.Sprintf("role %s has no authorization policy; propose/approve flow requires a policy-governed role", role.ShortString()), + ) + } + + id := types.NewPendingRoleChangeID(key, role, op, addresses) + + change, err := k.GetPendingRoleChange(ctx, id) + if err != nil { + return "", false, err + } + if change == nil { + change = &types.PendingRoleChange{ + Id: id, + Key: key, + Role: role, + Operation: op, + Addresses: addresses, + Proposer: proposer, + } + k.EmitEvent(ctx, types.NewEventRoleChangeProposed(change)) + } + + applied, err := k.recordApprovalAndMaybeApply(ctx, entry, change, proposer) + if err != nil { + return "", false, err + } + return id, applied, nil +} + +// ApproveRoleChange records an approval on an existing pending role change and applies it if the +// accumulated approvals now satisfy the role's policy. Returns whether the change was applied. +func (k Keeper) ApproveRoleChange(ctx context.Context, approver string, changeID string) (bool, error) { + change, err := k.GetPendingRoleChange(ctx, changeID) + if err != nil { + return false, err + } + if change == nil { + return false, types.NewErrCodePendingChangeNotFound(changeID) + } + + entry, err := k.GetRegistry(ctx, change.Key) + if err != nil { + return false, err + } + if entry == nil { + // The registry was removed out from under the pending change; clean it up. + if rerr := k.RemovePendingRoleChange(ctx, changeID); rerr != nil { + return false, rerr + } + return false, types.NewErrCodeRegistryNotFound(change.Key.String()) + } + + return k.recordApprovalAndMaybeApply(ctx, entry, change, approver) +} + +// recordApprovalAndMaybeApply adds approver to the change's approval set, evaluates the role's +// authorization policy against the accumulated approvals, and either applies and removes the +// change (returns true) or persists the updated change (returns false). +func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) (bool, error) { + if !slices.Contains(change.Approvals, approver) { + change.Approvals = append(change.Approvals, approver) + } + k.EmitEvent(ctx, types.NewEventRoleChangeApproved(change.Id, approver)) + + if !k.pendingChangeSatisfied(ctx, entry, change) { + if err := k.SetPendingRoleChange(ctx, *change); err != nil { + return false, err + } + return false, nil + } + + if err := k.applyPendingChange(ctx, change); err != nil { + return false, err + } + if err := k.RemovePendingRoleChange(ctx, change.Id); err != nil { + return false, err + } + k.EmitEvent(ctx, types.NewEventRoleChangeApplied(change)) + return true, nil +} + +// pendingChangeSatisfied reports whether the accumulated approvals satisfy the role's policy. +func (k Keeper) pendingChangeSatisfied(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange) bool { + roleAuth, ok := types.RoleAuthorizationMap()[change.Role] + if !ok { + return false + } + + // New addresses are only meaningful for grant operations (ASSIGNMENT_NEW checks). + var newAddrs []string + if change.Operation == types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT { + newAddrs = change.Addresses + } + + return k.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, newAddrs, change.Approvals) == nil +} + +// applyPendingChange performs the role grant or revoke described by the change. +func (k Keeper) applyPendingChange(ctx context.Context, change *types.PendingRoleChange) error { + switch change.Operation { + case types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT: + return k.GrantRole(ctx, change.Key, change.Role, change.Addresses) + case types.RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE: + return k.RevokeRole(ctx, change.Key, change.Role, change.Addresses) + default: + return types.NewErrCodeInvalidField("operation", "unsupported operation %s", change.Operation.ShortString()) + } +} diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go new file mode 100644 index 0000000000..ec3e2e5af8 --- /dev/null +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -0,0 +1,294 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/x/nft" + nftkeeper "cosmossdk.io/x/nft/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/authz" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// RoleChangeAccumulationAcceptanceTestSuite implements the ticket (sc-512248) Controller +// authorization matrix using Option B: single-signer messages whose approvals accumulate in +// registry state until the role's policy is satisfied, at which point the change auto-applies. +// +// Required approvals for a Controller update: +// - Current Controller +// - Current Secured Party for eNote (only if set) +// - New Controller +// +// Unlike the native multi-signer model, every message here is single-signer, so each approval is +// delegable via authz (see TestControllerUpdate_Accumulation_ViaAuthz). +type RoleChangeAccumulationAcceptanceTestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + registryKeeper keeper.Keeper + nftKeeper nftkeeper.Keeper + msgServer types.MsgServer + + nftClass nft.Class + + nftOwner string + currentController string + newController string + securedParty string + stranger string +} + +func TestRoleChangeAccumulationAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(RoleChangeAccumulationAcceptanceTestSuite)) +} + +func (s *RoleChangeAccumulationAcceptanceTestSuite) SetupTest() { + s.app = app.Setup(s.T()) + s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + + s.nftKeeper = s.app.NFTKeeper + s.registryKeeper = s.app.RegistryKeeper + s.msgServer = keeper.NewMsgServer(s.registryKeeper) + + s.nftOwner = genAddr() + s.currentController = genAddr() + s.newController = genAddr() + s.securedParty = genAddr() + s.stranger = genAddr() + + s.nftClass = nft.Class{Id: "accumulation-test-nft-class-id"} + s.nftKeeper.SaveClass(s.ctx, s.nftClass) +} + +func (s *RoleChangeAccumulationAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { + n := nft.NFT{ClassId: s.nftClass.Id, Id: id} + ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) + s.Require().NoError(err) + s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) + return &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} +} + +func (s *RoleChangeAccumulationAcceptanceTestSuite) setupRegistry(key *types.RegistryKey, roles []types.RolesEntry) { + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles)) +} + +// proposeNewController opens a pending GRANT of the CONTROLLER role to newController, signed by +// proposer, and returns the change id. +func (s *RoleChangeAccumulationAcceptanceTestSuite) proposeNewController(key *types.RegistryKey, proposer string) (string, bool) { + resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: proposer, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Operation: types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT, + Addresses: []string{s.newController}, + }) + s.Require().NoError(err) + return resp.ChangeId, resp.Applied +} + +// approve records an approval for the pending change, signed by signer. +func (s *RoleChangeAccumulationAcceptanceTestSuite) approve(changeID, signer string) bool { + resp, err := s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{ + Signer: signer, + ChangeId: changeID, + }) + s.Require().NoError(err) + return resp.Applied +} + +func (s *RoleChangeAccumulationAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { + entry, err := s.registryKeeper.GetRegistry(s.ctx, key) + s.Require().NoError(err) + s.Require().NotNil(entry) + for _, re := range entry.Roles { + if re.Role == role { + return re.Addresses + } + } + return nil +} + +// --- Scenario A: Controller set, no Secured Party for eNote --------------------------- + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerSet_NoSecuredParty() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + + baseRoles := func() []types.RolesEntry { + return []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + } + } + + s.Run("happy path: current controller proposes, new controller approves", func() { + key := s.mintNFT("acc-a-happy") + s.setupRegistry(key, baseRoles()) + + changeID, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied, "should not apply with only the current controller's approval") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + + applied = s.approve(changeID, s.newController) + s.Require().True(applied, "current + new controller approvals should satisfy the policy") + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: only current controller approves", func() { + key := s.mintNFT("acc-a-cc-only") + s.setupRegistry(key, baseRoles()) + + _, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied) + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: only new controller approves", func() { + key := s.mintNFT("acc-a-nc-only") + s.setupRegistry(key, baseRoles()) + + _, applied := s.proposeNewController(key, s.newController) + s.Require().False(applied, "missing current controller approval") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) +} + +// --- Scenario B: Controller set, Secured Party for eNote set -------------------------- + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerSet_WithSecuredParty() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + baseRoles := func() []types.RolesEntry { + return []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + } + } + + s.Run("happy path: current controller, secured party, and new controller approve", func() { + key := s.mintNFT("acc-b-happy") + s.setupRegistry(key, baseRoles()) + + changeID, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied) + + applied = s.approve(changeID, s.securedParty) + s.Require().False(applied, "still missing new controller") + + applied = s.approve(changeID, s.newController) + s.Require().True(applied, "all three required approvals present") + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: current controller and secured party (missing new controller)", func() { + key := s.mintNFT("acc-b-cc-sp") + s.setupRegistry(key, baseRoles()) + + changeID, _ := s.proposeNewController(key, s.currentController) + applied := s.approve(changeID, s.securedParty) + s.Require().False(applied) + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: current controller and new controller (missing secured party)", func() { + key := s.mintNFT("acc-b-cc-nc") + s.setupRegistry(key, baseRoles()) + + changeID, _ := s.proposeNewController(key, s.currentController) + applied := s.approve(changeID, s.newController) + s.Require().False(applied, "secured party approval is required when it is set") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: secured party and new controller (missing current controller)", func() { + key := s.mintNFT("acc-b-sp-nc") + s.setupRegistry(key, baseRoles()) + + changeID, _ := s.proposeNewController(key, s.securedParty) + applied := s.approve(changeID, s.newController) + s.Require().False(applied, "current controller approval is always required (no unilateral takeover)") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: a stranger approves", func() { + key := s.mintNFT("acc-b-stranger") + s.setupRegistry(key, baseRoles()) + + changeID, _ := s.proposeNewController(key, s.currentController) + applied := s.approve(changeID, s.stranger) + s.Require().False(applied, "an unrelated approval does not satisfy the policy") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) +} + +// --- AuthZ delegation ----------------------------------------------------------------- +// +// The single-signer accumulation model is the whole reason Option B satisfies the ticket's authz +// requirement: because each MsgApproveRoleChange has exactly one signer, a party can grant another +// account authority to submit its approval via authz MsgExec. This is impossible with the native +// multi-signer MsgGrantRole (see authorization_acceptance_test.go, +// TestControllerUpdate_ViaAuthzGrants). + +func (s *RoleChangeAccumulationAcceptanceTestSuite) mustAddr(addr string) sdk.AccAddress { + a, err := sdk.AccAddressFromBech32(addr) + s.Require().NoError(err) + return a +} + +// grantApproveAuthz grants executor authority to submit MsgApproveRoleChange on behalf of granter. +func (s *RoleChangeAccumulationAcceptanceTestSuite) grantApproveAuthz(executor, granter string) { + auth := authz.NewGenericAuthorization(sdk.MsgTypeURL(&types.MsgApproveRoleChange{})) + s.Require().NoError(s.app.AuthzKeeper.SaveGrant(s.ctx, s.mustAddr(executor), s.mustAddr(granter), auth, nil)) +} + +// execApproveViaAuthz submits MsgApproveRoleChange (signed by granter) wrapped in a MsgExec run by +// executor. +func (s *RoleChangeAccumulationAcceptanceTestSuite) execApproveViaAuthz(executor, granter, changeID string) { + inner := &types.MsgApproveRoleChange{Signer: granter, ChangeId: changeID} + msgExec := authz.NewMsgExec(s.mustAddr(executor), []sdk.Msg{inner}) + _, err := s.app.AuthzKeeper.Exec(s.ctx, &msgExec) + s.Require().NoError(err, "authz must dispatch a single-signer approval") +} + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_Accumulation_ViaAuthz() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + executor := genAddr() + + key := s.mintNFT("acc-authz") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + // Current controller proposes directly. + changeID, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied) + + // Secured party and new controller each delegate their approval to the executor via authz. + s.grantApproveAuthz(executor, s.securedParty) + s.grantApproveAuthz(executor, s.newController) + + s.execApproveViaAuthz(executor, s.securedParty, changeID) + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController, + "not yet applied after only the secured party's delegated approval") + + s.execApproveViaAuthz(executor, s.newController, changeID) + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController, + "change auto-applies once all required approvals (incl. authz-delegated ones) accumulate") + + // The pending change should have been cleaned up after applying. + pending, err := s.registryKeeper.GetPendingRoleChange(s.ctx, changeID) + s.Require().NoError(err) + s.Require().Nil(pending, "pending change should be removed after it applies") +} diff --git a/x/registry/types/authorization.pb.go b/x/registry/types/authorization.pb.go index e0d1b00108..a62fb8bae0 100644 --- a/x/registry/types/authorization.pb.go +++ b/x/registry/types/authorization.pb.go @@ -324,7 +324,6 @@ type RoleAssignment struct { // role_selector identifies the role to resolve, either a single role or a priority list. // // Types that are valid to be assigned to RoleSelector: - // // *RoleAssignment_RegistryRole // *RoleAssignment_NftRole // *RoleAssignment_RolePriority diff --git a/x/registry/types/errors.go b/x/registry/types/errors.go index e3c307aa18..fb79880f44 100644 --- a/x/registry/types/errors.go +++ b/x/registry/types/errors.go @@ -38,6 +38,7 @@ const ( ErrCodeInvalidKey ErrCode = "INVALID_KEY" ErrCodeAddressDoesNotHaveRole ErrCode = "ADDRESS_DOES_NOT_HAVE_ROLE" ErrCodeInvalidField ErrCode = "INVALID_FIELD" + ErrCodePendingChangeNotFound ErrCode = "PENDING_CHANGE_NOT_FOUND" ) var ( @@ -50,6 +51,7 @@ var ( ErrInvalidKey = cerrs.Register(ModuleName, 7, "invalid key") ErrAddressDoesNotHaveRole = cerrs.Register(ModuleName, 8, "address does not have role") ErrInvalidField = cerrs.Register(ModuleName, 9, "invalid field") + ErrPendingChangeNotFound = cerrs.Register(ModuleName, 10, "pending role change not found") ) func NewErrCodeRegistryAlreadyExists(key string) error { @@ -83,3 +85,7 @@ func NewErrCodeAddressDoesNotHaveRole(address, role string) error { func NewErrCodeInvalidField(field, format string, args ...interface{}) error { return cerrs.Wrapf(ErrInvalidField, "invalid %s: %s", field, fmt.Sprintf(format, args...)) } + +func NewErrCodePendingChangeNotFound(changeID string) error { + return cerrs.Wrapf(ErrPendingChangeNotFound, "pending role change not found: %q", changeID) +} diff --git a/x/registry/types/events.go b/x/registry/types/events.go index 4baa0d01dc..f2e6bb00ef 100644 --- a/x/registry/types/events.go +++ b/x/registry/types/events.go @@ -49,6 +49,37 @@ func NewEventRegistryBulkUpdated(key *RegistryKey) *EventRegistryBulkUpdated { } } +// NewEventRoleChangeProposed returns a new EventRoleChangeProposed. +func NewEventRoleChangeProposed(change *PendingRoleChange) *EventRoleChangeProposed { + return &EventRoleChangeProposed{ + NftId: change.Key.NftId, + AssetClassId: change.Key.AssetClassId, + ChangeId: change.Id, + Role: change.Role.ShortString(), + Operation: change.Operation.ShortString(), + Proposer: change.Proposer, + } +} + +// NewEventRoleChangeApproved returns a new EventRoleChangeApproved. +func NewEventRoleChangeApproved(changeID, approver string) *EventRoleChangeApproved { + return &EventRoleChangeApproved{ + ChangeId: changeID, + Approver: approver, + } +} + +// NewEventRoleChangeApplied returns a new EventRoleChangeApplied. +func NewEventRoleChangeApplied(change *PendingRoleChange) *EventRoleChangeApplied { + return &EventRoleChangeApplied{ + NftId: change.Key.NftId, + AssetClassId: change.Key.AssetClassId, + ChangeId: change.Id, + Role: change.Role.ShortString(), + Operation: change.Operation.ShortString(), + } +} + // GetChangeEvents gets all the events that represent the changes from oldReg to newReg. // Panics if they have different keys (unless oldReg or newReg is nil). func GetChangeEvents(oldReg, newReg *RegistryEntry) ([]*EventRoleGranted, []*EventRoleRevoked) { diff --git a/x/registry/types/events.pb.go b/x/registry/types/events.pb.go index 5c04924007..6d255c81a7 100644 --- a/x/registry/types/events.pb.go +++ b/x/registry/types/events.pb.go @@ -319,12 +319,230 @@ func (m *EventRegistryBulkUpdated) GetAssetClassId() string { return "" } +// EventRoleChangeProposed is emitted when a pending role change is opened. +type EventRoleChangeProposed struct { + NftId string `protobuf:"bytes,1,opt,name=nft_id,json=nftId,proto3" json:"nft_id,omitempty"` + AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` + ChangeId string `protobuf:"bytes,3,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` + Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` + Operation string `protobuf:"bytes,5,opt,name=operation,proto3" json:"operation,omitempty"` + Proposer string `protobuf:"bytes,6,opt,name=proposer,proto3" json:"proposer,omitempty"` +} + +func (m *EventRoleChangeProposed) Reset() { *m = EventRoleChangeProposed{} } +func (m *EventRoleChangeProposed) String() string { return proto.CompactTextString(m) } +func (*EventRoleChangeProposed) ProtoMessage() {} +func (*EventRoleChangeProposed) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{5} +} +func (m *EventRoleChangeProposed) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRoleChangeProposed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRoleChangeProposed.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRoleChangeProposed) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRoleChangeProposed.Merge(m, src) +} +func (m *EventRoleChangeProposed) XXX_Size() int { + return m.Size() +} +func (m *EventRoleChangeProposed) XXX_DiscardUnknown() { + xxx_messageInfo_EventRoleChangeProposed.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRoleChangeProposed proto.InternalMessageInfo + +func (m *EventRoleChangeProposed) GetNftId() string { + if m != nil { + return m.NftId + } + return "" +} + +func (m *EventRoleChangeProposed) GetAssetClassId() string { + if m != nil { + return m.AssetClassId + } + return "" +} + +func (m *EventRoleChangeProposed) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +func (m *EventRoleChangeProposed) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *EventRoleChangeProposed) GetOperation() string { + if m != nil { + return m.Operation + } + return "" +} + +func (m *EventRoleChangeProposed) GetProposer() string { + if m != nil { + return m.Proposer + } + return "" +} + +// EventRoleChangeApproved is emitted when a party records an approval for a pending role change. +type EventRoleChangeApproved struct { + ChangeId string `protobuf:"bytes,1,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` + Approver string `protobuf:"bytes,2,opt,name=approver,proto3" json:"approver,omitempty"` +} + +func (m *EventRoleChangeApproved) Reset() { *m = EventRoleChangeApproved{} } +func (m *EventRoleChangeApproved) String() string { return proto.CompactTextString(m) } +func (*EventRoleChangeApproved) ProtoMessage() {} +func (*EventRoleChangeApproved) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{6} +} +func (m *EventRoleChangeApproved) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRoleChangeApproved) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRoleChangeApproved.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRoleChangeApproved) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRoleChangeApproved.Merge(m, src) +} +func (m *EventRoleChangeApproved) XXX_Size() int { + return m.Size() +} +func (m *EventRoleChangeApproved) XXX_DiscardUnknown() { + xxx_messageInfo_EventRoleChangeApproved.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRoleChangeApproved proto.InternalMessageInfo + +func (m *EventRoleChangeApproved) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +func (m *EventRoleChangeApproved) GetApprover() string { + if m != nil { + return m.Approver + } + return "" +} + +// EventRoleChangeApplied is emitted when a pending role change accumulates enough approvals and is applied. +type EventRoleChangeApplied struct { + NftId string `protobuf:"bytes,1,opt,name=nft_id,json=nftId,proto3" json:"nft_id,omitempty"` + AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` + ChangeId string `protobuf:"bytes,3,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` + Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` + Operation string `protobuf:"bytes,5,opt,name=operation,proto3" json:"operation,omitempty"` +} + +func (m *EventRoleChangeApplied) Reset() { *m = EventRoleChangeApplied{} } +func (m *EventRoleChangeApplied) String() string { return proto.CompactTextString(m) } +func (*EventRoleChangeApplied) ProtoMessage() {} +func (*EventRoleChangeApplied) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{7} +} +func (m *EventRoleChangeApplied) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRoleChangeApplied) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRoleChangeApplied.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRoleChangeApplied) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRoleChangeApplied.Merge(m, src) +} +func (m *EventRoleChangeApplied) XXX_Size() int { + return m.Size() +} +func (m *EventRoleChangeApplied) XXX_DiscardUnknown() { + xxx_messageInfo_EventRoleChangeApplied.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRoleChangeApplied proto.InternalMessageInfo + +func (m *EventRoleChangeApplied) GetNftId() string { + if m != nil { + return m.NftId + } + return "" +} + +func (m *EventRoleChangeApplied) GetAssetClassId() string { + if m != nil { + return m.AssetClassId + } + return "" +} + +func (m *EventRoleChangeApplied) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +func (m *EventRoleChangeApplied) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *EventRoleChangeApplied) GetOperation() string { + if m != nil { + return m.Operation + } + return "" +} + func init() { proto.RegisterType((*EventNFTRegistered)(nil), "provenance.registry.v1.EventNFTRegistered") proto.RegisterType((*EventRoleGranted)(nil), "provenance.registry.v1.EventRoleGranted") proto.RegisterType((*EventRoleRevoked)(nil), "provenance.registry.v1.EventRoleRevoked") proto.RegisterType((*EventNFTUnregistered)(nil), "provenance.registry.v1.EventNFTUnregistered") proto.RegisterType((*EventRegistryBulkUpdated)(nil), "provenance.registry.v1.EventRegistryBulkUpdated") + proto.RegisterType((*EventRoleChangeProposed)(nil), "provenance.registry.v1.EventRoleChangeProposed") + proto.RegisterType((*EventRoleChangeApproved)(nil), "provenance.registry.v1.EventRoleChangeApproved") + proto.RegisterType((*EventRoleChangeApplied)(nil), "provenance.registry.v1.EventRoleChangeApplied") } func init() { @@ -332,26 +550,33 @@ func init() { } var fileDescriptor_61a0995529587ff0 = []byte{ - // 304 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2e, 0x28, 0xca, 0x2f, - 0x4b, 0xcd, 0x4b, 0xcc, 0x4b, 0x4e, 0xd5, 0x2f, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, 0x29, 0xaa, 0xd4, - 0x2f, 0x33, 0xd4, 0x4f, 0x2d, 0x4b, 0xcd, 0x2b, 0x29, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, - 0x12, 0x43, 0x28, 0xd2, 0x83, 0x29, 0xd2, 0x2b, 0x33, 0x54, 0x0a, 0xe4, 0x12, 0x72, 0x05, 0xa9, - 0xf3, 0x73, 0x0b, 0x09, 0x02, 0x0b, 0xa7, 0x16, 0xa5, 0xa6, 0x08, 0x89, 0x72, 0xb1, 0xe5, 0xa5, - 0x95, 0xc4, 0x67, 0xa6, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0xb1, 0xe6, 0xa5, 0x95, 0x78, - 0xa6, 0x08, 0xa9, 0x70, 0xf1, 0x25, 0x16, 0x17, 0xa7, 0x96, 0xc4, 0x27, 0xe7, 0x24, 0x16, 0x17, - 0x83, 0xa4, 0x99, 0xc0, 0xd2, 0x3c, 0x60, 0x51, 0x67, 0x90, 0xa0, 0x67, 0x8a, 0x52, 0x23, 0x23, - 0x97, 0x00, 0xd8, 0xcc, 0xa0, 0xfc, 0x9c, 0x54, 0xf7, 0xa2, 0xc4, 0xbc, 0x12, 0x0a, 0x4d, 0x14, - 0x12, 0xe2, 0x62, 0x29, 0xca, 0xcf, 0x49, 0x95, 0x60, 0x06, 0xcb, 0x81, 0xd9, 0x42, 0x32, 0x5c, - 0x9c, 0x89, 0x29, 0x29, 0x45, 0xa9, 0xc5, 0xc5, 0xa9, 0xc5, 0x12, 0x2c, 0x0a, 0xcc, 0x1a, 0x9c, - 0x41, 0x08, 0x01, 0x54, 0x37, 0x04, 0xa5, 0x96, 0xe5, 0x67, 0xd3, 0xdf, 0x0d, 0xc1, 0x5c, 0x22, - 0xb0, 0xa0, 0x0d, 0xcd, 0x2b, 0xa2, 0x52, 0xe0, 0x86, 0x73, 0x49, 0x40, 0xfc, 0x05, 0x8d, 0x43, - 0xa7, 0xd2, 0x9c, 0xec, 0xd0, 0x82, 0x94, 0x44, 0x4a, 0xc3, 0xd8, 0x29, 0xfb, 0xc4, 0x23, 0x39, - 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, - 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xb8, 0x24, 0x33, 0xf3, 0xf5, 0xb0, 0xa7, 0x9e, 0x00, 0xc6, - 0x28, 0x93, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x84, 0x22, 0xdd, - 0xcc, 0x7c, 0x24, 0x9e, 0x7e, 0x05, 0x22, 0x5d, 0x96, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, - 0x13, 0xa5, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x21, 0x15, 0x91, 0xbb, 0x02, 0x00, 0x00, + // 406 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x94, 0xcf, 0xae, 0xd2, 0x40, + 0x18, 0xc5, 0x19, 0xf9, 0x13, 0x3a, 0x31, 0xc6, 0x4c, 0x14, 0x2b, 0x9a, 0x86, 0x54, 0x17, 0x6c, + 0x6c, 0x43, 0xf4, 0x05, 0x84, 0xa8, 0x61, 0x63, 0xb0, 0x4a, 0x4c, 0xdc, 0x90, 0xa1, 0xfd, 0x80, + 0x86, 0x3a, 0x33, 0x99, 0x19, 0x1a, 0x59, 0xfa, 0x06, 0x3e, 0x84, 0x2f, 0xe2, 0xce, 0x25, 0x4b, + 0x97, 0x06, 0x5e, 0xe4, 0xa6, 0x53, 0xa0, 0xfc, 0xb9, 0x3b, 0x6e, 0x6e, 0xee, 0xae, 0x73, 0xbe, + 0x93, 0x93, 0xf3, 0xfd, 0xda, 0x0e, 0x7e, 0x21, 0x24, 0x4f, 0x81, 0x51, 0x16, 0x82, 0x2f, 0x61, + 0x1a, 0x2b, 0x2d, 0x97, 0x7e, 0xda, 0xf1, 0x21, 0x05, 0xa6, 0x95, 0x27, 0x24, 0xd7, 0x9c, 0x34, + 0x0a, 0x93, 0xb7, 0x33, 0x79, 0x69, 0xc7, 0xfd, 0x84, 0xc9, 0xbb, 0xcc, 0xf7, 0xf1, 0xfd, 0x97, + 0xc0, 0xc8, 0x20, 0x21, 0x22, 0x8f, 0x71, 0x8d, 0x4d, 0xf4, 0x28, 0x8e, 0x6c, 0xd4, 0x42, 0x6d, + 0x2b, 0xa8, 0xb2, 0x89, 0xee, 0x47, 0xe4, 0x25, 0x7e, 0x40, 0x95, 0x02, 0x3d, 0x0a, 0x13, 0xaa, + 0x54, 0x36, 0xbe, 0x67, 0xc6, 0xf7, 0x8d, 0xda, 0xcb, 0xc4, 0x7e, 0xe4, 0xfe, 0x44, 0xf8, 0xa1, + 0xc9, 0x0c, 0x78, 0x02, 0x1f, 0x24, 0x65, 0xfa, 0xc2, 0x44, 0x42, 0x70, 0x45, 0xf2, 0x04, 0xec, + 0xb2, 0x99, 0x99, 0x67, 0xf2, 0x1c, 0x5b, 0x34, 0x8a, 0x24, 0x28, 0x05, 0xca, 0xae, 0xb4, 0xca, + 0x6d, 0x2b, 0x28, 0x84, 0xe3, 0x0e, 0x01, 0xa4, 0x7c, 0x7e, 0xfb, 0x1d, 0x3e, 0xe3, 0x47, 0x3b, + 0xb4, 0x43, 0x26, 0x6f, 0x08, 0xee, 0x57, 0x6c, 0xe7, 0x7b, 0x6d, 0xdf, 0x61, 0x77, 0x91, 0xcc, + 0x87, 0x22, 0xa2, 0x97, 0x32, 0x76, 0xff, 0x20, 0xfc, 0x64, 0x4f, 0xac, 0x37, 0xa3, 0x6c, 0x0a, + 0x03, 0xc9, 0x05, 0x57, 0x97, 0x82, 0x7b, 0x86, 0xad, 0xd0, 0xc4, 0x65, 0x86, 0x9c, 0x5e, 0x3d, + 0x17, 0x0e, 0xa8, 0x56, 0x8e, 0xa9, 0x72, 0x01, 0x92, 0xea, 0x98, 0x33, 0xbb, 0x6a, 0x06, 0x85, + 0x40, 0x9a, 0xb8, 0x2e, 0xf2, 0x5e, 0xd2, 0xae, 0xe5, 0x69, 0xbb, 0xb3, 0x1b, 0x9c, 0xad, 0xf0, + 0x56, 0x98, 0xef, 0xfe, 0xa4, 0x05, 0x3a, 0x69, 0xd1, 0xc4, 0x75, 0x9a, 0x1b, 0xe5, 0x76, 0x85, + 0xfd, 0xd9, 0xfd, 0x8d, 0x70, 0xe3, 0x3c, 0x34, 0x89, 0xef, 0x16, 0x96, 0xee, 0xfc, 0xef, 0xda, + 0x41, 0xab, 0xb5, 0x83, 0xfe, 0xaf, 0x1d, 0xf4, 0x6b, 0xe3, 0x94, 0x56, 0x1b, 0xa7, 0xf4, 0x6f, + 0xe3, 0x94, 0xf0, 0xd3, 0x98, 0x7b, 0xd7, 0xff, 0xfc, 0x03, 0xf4, 0xed, 0xcd, 0x34, 0xd6, 0xb3, + 0xc5, 0xd8, 0x0b, 0xf9, 0x77, 0xbf, 0x30, 0xbd, 0x8a, 0xf9, 0xc1, 0xc9, 0xff, 0x51, 0x5c, 0x2b, + 0x7a, 0x29, 0x40, 0x8d, 0x6b, 0xe6, 0x4e, 0x79, 0x7d, 0x15, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x08, + 0x35, 0x24, 0x7a, 0x04, 0x00, 0x00, } func (m *EventNFTRegistered) Marshal() (dAtA []byte, err error) { @@ -571,6 +796,166 @@ func (m *EventRegistryBulkUpdated) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } +func (m *EventRoleChangeProposed) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRoleChangeProposed) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRoleChangeProposed) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Proposer) > 0 { + i -= len(m.Proposer) + copy(dAtA[i:], m.Proposer) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Proposer))) + i-- + dAtA[i] = 0x32 + } + if len(m.Operation) > 0 { + i -= len(m.Operation) + copy(dAtA[i:], m.Operation) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Operation))) + i-- + dAtA[i] = 0x2a + } + if len(m.Role) > 0 { + i -= len(m.Role) + copy(dAtA[i:], m.Role) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Role))) + i-- + dAtA[i] = 0x22 + } + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0x1a + } + if len(m.AssetClassId) > 0 { + i -= len(m.AssetClassId) + copy(dAtA[i:], m.AssetClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.AssetClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.NftId) > 0 { + i -= len(m.NftId) + copy(dAtA[i:], m.NftId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.NftId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventRoleChangeApproved) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRoleChangeApproved) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRoleChangeApproved) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Approver) > 0 { + i -= len(m.Approver) + copy(dAtA[i:], m.Approver) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Approver))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventRoleChangeApplied) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRoleChangeApplied) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRoleChangeApplied) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Operation) > 0 { + i -= len(m.Operation) + copy(dAtA[i:], m.Operation) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Operation))) + i-- + dAtA[i] = 0x2a + } + if len(m.Role) > 0 { + i -= len(m.Role) + copy(dAtA[i:], m.Role) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Role))) + i-- + dAtA[i] = 0x22 + } + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0x1a + } + if len(m.AssetClassId) > 0 { + i -= len(m.AssetClassId) + copy(dAtA[i:], m.AssetClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.AssetClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.NftId) > 0 { + i -= len(m.NftId) + copy(dAtA[i:], m.NftId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.NftId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -687,6 +1072,85 @@ func (m *EventRegistryBulkUpdated) Size() (n int) { return n } +func (m *EventRoleChangeProposed) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.NftId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Role) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Operation) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Proposer) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventRoleChangeApproved) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Approver) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventRoleChangeApplied) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.NftId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Role) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Operation) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -724,7 +1188,363 @@ func (m *EventNFTRegistered) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NftId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRoleGranted: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRoleGranted: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NftId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Role = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRoleRevoked: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRoleRevoked: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NftId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -752,11 +1572,11 @@ func (m *EventNFTRegistered) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NftId = string(dAtA[iNdEx:postIndex]) + m.Role = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -784,7 +1604,7 @@ func (m *EventNFTRegistered) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AssetClassId = string(dAtA[iNdEx:postIndex]) + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex @@ -807,7 +1627,7 @@ func (m *EventNFTRegistered) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { +func (m *EventNFTUnregistered) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -830,10 +1650,10 @@ func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventRoleGranted: wiretype end group for non-group") + return fmt.Errorf("proto: EventNFTUnregistered: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventRoleGranted: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventNFTUnregistered: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -900,9 +1720,59 @@ func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { } m.AssetClassId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventRegistryBulkUpdated) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRegistryBulkUpdated: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRegistryBulkUpdated: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -930,11 +1800,11 @@ func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Role = string(dAtA[iNdEx:postIndex]) + m.NftId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -962,7 +1832,7 @@ func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + m.AssetClassId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -985,7 +1855,7 @@ func (m *EventRoleGranted) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { +func (m *EventRoleChangeProposed) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1008,10 +1878,10 @@ func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventRoleRevoked: wiretype end group for non-group") + return fmt.Errorf("proto: EventRoleChangeProposed: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventRoleRevoked: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventRoleChangeProposed: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1079,6 +1949,38 @@ func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { m.AssetClassId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) } @@ -1110,9 +2012,9 @@ func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { } m.Role = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1140,7 +2042,39 @@ func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + m.Operation = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proposer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proposer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -1163,7 +2097,7 @@ func (m *EventRoleRevoked) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventNFTUnregistered) Unmarshal(dAtA []byte) error { +func (m *EventRoleChangeApproved) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1186,15 +2120,15 @@ func (m *EventNFTUnregistered) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventNFTUnregistered: wiretype end group for non-group") + return fmt.Errorf("proto: EventRoleChangeApproved: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventNFTUnregistered: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventRoleChangeApproved: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1222,11 +2156,11 @@ func (m *EventNFTUnregistered) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NftId = string(dAtA[iNdEx:postIndex]) + m.ChangeId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Approver", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1254,7 +2188,7 @@ func (m *EventNFTUnregistered) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AssetClassId = string(dAtA[iNdEx:postIndex]) + m.Approver = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -1277,7 +2211,7 @@ func (m *EventNFTUnregistered) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventRegistryBulkUpdated) Unmarshal(dAtA []byte) error { +func (m *EventRoleChangeApplied) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1300,10 +2234,10 @@ func (m *EventRegistryBulkUpdated) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: EventRegistryBulkUpdated: wiretype end group for non-group") + return fmt.Errorf("proto: EventRoleChangeApplied: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: EventRegistryBulkUpdated: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: EventRoleChangeApplied: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1370,6 +2304,102 @@ func (m *EventRegistryBulkUpdated) Unmarshal(dAtA []byte) error { } m.AssetClassId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Role = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Operation = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 7c0d2ecf9c..6832a21c1e 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -15,6 +15,8 @@ var AllRequestMsgs = []sdk.Msg{ (*MsgUnregisterNFT)(nil), (*MsgRegistryBulkUpdate)(nil), (*MsgSetRoles)(nil), + (*MsgProposeRoleChange)(nil), + (*MsgApproveRoleChange)(nil), } // ValidateBasic validates the MsgRegisterNFT message @@ -130,6 +132,46 @@ func (m MsgSetRoles) ValidateBasic() error { return errors.Join(errs...) } +// ValidateBasic validates the MsgProposeRoleChange message +func (m MsgProposeRoleChange) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + } + + if err := m.Key.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("key", "%s", err)) + } + + if err := m.Role.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("role", "%s", err)) + } + + if m.Operation == RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED { + errs = append(errs, NewErrCodeInvalidField("operation", "operation cannot be unspecified")) + } + + if err := validateAddresses(m.Addresses); err != nil { + errs = append(errs, NewErrCodeInvalidField("addresses", "%s", err)) + } + + return errors.Join(errs...) +} + +// ValidateBasic validates the MsgApproveRoleChange message +func (m MsgApproveRoleChange) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + } + + if len(m.ChangeId) == 0 { + errs = append(errs, NewErrCodeInvalidField("change_id", "change_id is required")) + } + + return errors.Join(errs...) +} + // ValidateBasic validates the MsgUnregisterNFT message func (m MsgUnregisterNFT) ValidateBasic() error { var errs []error diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 2f18b05980..f6e31f8f77 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -15,6 +15,8 @@ func TestAllMsgsGetSigners(t *testing.T) { func(signer string) sdk.Msg { return &MsgRegisterNFT{Signer: signer} }, func(signer string) sdk.Msg { return &MsgUnregisterNFT{Signer: signer} }, func(signer string) sdk.Msg { return &MsgRegistryBulkUpdate{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgProposeRoleChange{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgApproveRoleChange{Signer: signer} }, } msgMakersMulti := []testutil.MsgMakerMulti{ func(signers []string) sdk.Msg { return &MsgGrantRole{Signers: signers} }, @@ -152,6 +154,65 @@ func TestMsgSetRoles_ValidateBasic(t *testing.T) { } } +func TestMsgProposeRoleChange_ValidateBasic(t *testing.T) { + validAddr := sdk.AccAddress("propose_signer_________").String() + otherAddr := sdk.AccAddress("propose_target_________").String() + validKey := &RegistryKey{AssetClassId: "aclass", NftId: "nft1"} + grant := RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT + + tests := []struct { + name string + msg MsgProposeRoleChange + exp string + }{ + {name: "valid", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}}, + {name: "empty signer", msg: MsgProposeRoleChange{Signer: "", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid signer: empty address"}, + {name: "bad signer", msg: MsgProposeRoleChange{Signer: "bad", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid signer: decoding bech32"}, + {name: "nil key", msg: MsgProposeRoleChange{Signer: validAddr, Key: nil, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, + {name: "invalid role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, + {name: "unspecified operation", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid operation: operation cannot be unspecified"}, + {name: "no addresses", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, + {name: "bad address", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.exp == "" { + assertions.RequireErrorContents(t, err, nil) + } else { + assertions.RequireErrorContents(t, err, []string{tc.exp}) + } + }) + } +} + +func TestMsgApproveRoleChange_ValidateBasic(t *testing.T) { + validAddr := sdk.AccAddress("approve_signer_________").String() + + tests := []struct { + name string + msg MsgApproveRoleChange + exp string + }{ + {name: "valid", msg: MsgApproveRoleChange{Signer: validAddr, ChangeId: "abc123"}}, + {name: "empty signer", msg: MsgApproveRoleChange{Signer: "", ChangeId: "abc123"}, exp: "invalid signer: empty address"}, + {name: "bad signer", msg: MsgApproveRoleChange{Signer: "bad", ChangeId: "abc123"}, exp: "invalid signer: decoding bech32"}, + {name: "empty change_id", msg: MsgApproveRoleChange{Signer: validAddr, ChangeId: ""}, exp: "invalid change_id: change_id is required"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.exp == "" { + assertions.RequireErrorContents(t, err, nil) + } else { + assertions.RequireErrorContents(t, err, []string{tc.exp}) + } + }) + } +} + func TestMsgUnregisterNFT_ValidateBasic(t *testing.T) { validAddr := sdk.AccAddress("unreg_signer___________").String() validKey := &RegistryKey{AssetClassId: "aclass", NftId: "nft1"} diff --git a/x/registry/types/pending.go b/x/registry/types/pending.go new file mode 100644 index 0000000000..4445954ee7 --- /dev/null +++ b/x/registry/types/pending.go @@ -0,0 +1,38 @@ +package types + +import ( + "crypto/sha256" + "encoding/hex" + "slices" + "strconv" + "strings" +) + +// ShortString returns a compact human-readable form of the operation (e.g. "GRANT"). +func (o RoleChangeOperation) ShortString() string { + return strings.TrimPrefix(o.String(), "ROLE_CHANGE_OPERATION_") +} + +// NewPendingRoleChangeID computes the deterministic identifier for a pending role change. +// +// The id is a hash of the registry key, role, operation, and the (order-independent) set of +// target addresses. Two proposals describing the same change therefore collapse onto the same +// pending change, so their approvals accumulate together. +func NewPendingRoleChangeID(key *RegistryKey, role RegistryRole, op RoleChangeOperation, addresses []string) string { + sorted := slices.Clone(addresses) + slices.Sort(sorted) + + var b strings.Builder + b.WriteString(key.AssetClassId) + b.WriteByte(0) + b.WriteString(key.NftId) + b.WriteByte(0) + b.WriteString(strconv.Itoa(int(role))) + b.WriteByte(0) + b.WriteString(strconv.Itoa(int(op))) + b.WriteByte(0) + b.WriteString(strings.Join(sorted, ",")) + + sum := sha256.Sum256([]byte(b.String())) + return hex.EncodeToString(sum[:]) +} diff --git a/x/registry/types/registry.pb.go b/x/registry/types/registry.pb.go index 66f429cd12..d8ebb5ce65 100644 --- a/x/registry/types/registry.pb.go +++ b/x/registry/types/registry.pb.go @@ -99,6 +99,38 @@ func (RegistryRole) EnumDescriptor() ([]byte, []int) { return fileDescriptor_2fa7a0b4d34d0208, []int{0} } +// RoleChangeOperation identifies the kind of role change a pending change applies. +type RoleChangeOperation int32 + +const ( + // ROLE_CHANGE_OPERATION_UNSPECIFIED is the default, invalid operation. + RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED RoleChangeOperation = 0 + // ROLE_CHANGE_OPERATION_GRANT adds the target addresses to the role when applied. + RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT RoleChangeOperation = 1 + // ROLE_CHANGE_OPERATION_REVOKE removes the target addresses from the role when applied. + RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE RoleChangeOperation = 2 +) + +var RoleChangeOperation_name = map[int32]string{ + 0: "ROLE_CHANGE_OPERATION_UNSPECIFIED", + 1: "ROLE_CHANGE_OPERATION_GRANT", + 2: "ROLE_CHANGE_OPERATION_REVOKE", +} + +var RoleChangeOperation_value = map[string]int32{ + "ROLE_CHANGE_OPERATION_UNSPECIFIED": 0, + "ROLE_CHANGE_OPERATION_GRANT": 1, + "ROLE_CHANGE_OPERATION_REVOKE": 2, +} + +func (x RoleChangeOperation) String() string { + return proto.EnumName(RoleChangeOperation_name, int32(x)) +} + +func (RoleChangeOperation) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_2fa7a0b4d34d0208, []int{1} +} + // RegistryKey represents a unique identifier for registry entries. // It links registry entries to specific NFT assets and their associated asset classes. type RegistryKey struct { @@ -274,11 +306,116 @@ func (m *RolesEntry) GetAddresses() []string { return nil } +// PendingRoleChange is a role change awaiting the approvals required by the role's authorization +// policy. Each required party submits a single-signer approval; once the accumulated approvers +// satisfy the policy, the change is applied and this record is removed. +type PendingRoleChange struct { + // id is the deterministic identifier of this pending change + // (a hash of key + role + operation + sorted addresses). + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // key identifies the registry entry the change applies to. + Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // role is the role being changed. + Role RegistryRole `protobuf:"varint,3,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` + // operation is the kind of change to apply (grant or revoke) once approved. + Operation RoleChangeOperation `protobuf:"varint,4,opt,name=operation,proto3,enum=provenance.registry.v1.RoleChangeOperation" json:"operation,omitempty"` + // addresses is the set of addresses the operation applies to. + Addresses []string `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` + // proposer is the address that opened the pending change. + Proposer string `protobuf:"bytes,6,opt,name=proposer,proto3" json:"proposer,omitempty"` + // approvals is the accumulated set of addresses that have approved this change. + Approvals []string `protobuf:"bytes,7,rep,name=approvals,proto3" json:"approvals,omitempty"` +} + +func (m *PendingRoleChange) Reset() { *m = PendingRoleChange{} } +func (m *PendingRoleChange) String() string { return proto.CompactTextString(m) } +func (*PendingRoleChange) ProtoMessage() {} +func (*PendingRoleChange) Descriptor() ([]byte, []int) { + return fileDescriptor_2fa7a0b4d34d0208, []int{3} +} +func (m *PendingRoleChange) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PendingRoleChange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PendingRoleChange.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *PendingRoleChange) XXX_Merge(src proto.Message) { + xxx_messageInfo_PendingRoleChange.Merge(m, src) +} +func (m *PendingRoleChange) XXX_Size() int { + return m.Size() +} +func (m *PendingRoleChange) XXX_DiscardUnknown() { + xxx_messageInfo_PendingRoleChange.DiscardUnknown(m) +} + +var xxx_messageInfo_PendingRoleChange proto.InternalMessageInfo + +func (m *PendingRoleChange) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *PendingRoleChange) GetKey() *RegistryKey { + if m != nil { + return m.Key + } + return nil +} + +func (m *PendingRoleChange) GetRole() RegistryRole { + if m != nil { + return m.Role + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *PendingRoleChange) GetOperation() RoleChangeOperation { + if m != nil { + return m.Operation + } + return RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED +} + +func (m *PendingRoleChange) GetAddresses() []string { + if m != nil { + return m.Addresses + } + return nil +} + +func (m *PendingRoleChange) GetProposer() string { + if m != nil { + return m.Proposer + } + return "" +} + +func (m *PendingRoleChange) GetApprovals() []string { + if m != nil { + return m.Approvals + } + return nil +} + func init() { proto.RegisterEnum("provenance.registry.v1.RegistryRole", RegistryRole_name, RegistryRole_value) + proto.RegisterEnum("provenance.registry.v1.RoleChangeOperation", RoleChangeOperation_name, RoleChangeOperation_value) proto.RegisterType((*RegistryKey)(nil), "provenance.registry.v1.RegistryKey") proto.RegisterType((*RegistryEntry)(nil), "provenance.registry.v1.RegistryEntry") proto.RegisterType((*RolesEntry)(nil), "provenance.registry.v1.RolesEntry") + proto.RegisterType((*PendingRoleChange)(nil), "provenance.registry.v1.PendingRoleChange") } func init() { @@ -286,41 +423,50 @@ func init() { } var fileDescriptor_2fa7a0b4d34d0208 = []byte{ - // 532 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0xcb, 0x6e, 0xd3, 0x40, - 0x14, 0x86, 0xed, 0xdc, 0x20, 0x27, 0xa5, 0xb2, 0x46, 0x6d, 0x49, 0x02, 0xb8, 0x55, 0x48, 0xa5, - 0x80, 0xd4, 0x44, 0x0d, 0x17, 0x21, 0x16, 0x48, 0xb9, 0x4c, 0x23, 0x8b, 0xc8, 0x8e, 0x8e, 0x1d, - 0xaa, 0xb2, 0xb1, 0xd2, 0xc4, 0x0d, 0x56, 0x5b, 0x4f, 0xe4, 0x31, 0x11, 0xd9, 0xb0, 0x64, 0xcd, - 0x92, 0x25, 0x0f, 0xc1, 0x43, 0x74, 0x59, 0xb1, 0x62, 0x85, 0x50, 0xb2, 0xe4, 0x25, 0x90, 0xed, - 0x90, 0x4b, 0xd3, 0xaa, 0xbb, 0x99, 0xf3, 0x7d, 0xff, 0x99, 0x63, 0x8f, 0x06, 0x76, 0x07, 0x2e, - 0x1b, 0x5a, 0x4e, 0xc7, 0xe9, 0x5a, 0x25, 0xd7, 0xea, 0xdb, 0xdc, 0x73, 0x47, 0xa5, 0xe1, 0xfe, - 0x6c, 0x5d, 0x1c, 0xb8, 0xcc, 0x63, 0x64, 0x6b, 0xae, 0x15, 0x67, 0x68, 0xb8, 0x9f, 0xdd, 0xe8, - 0xb3, 0x3e, 0x0b, 0x94, 0x92, 0xbf, 0x0a, 0xed, 0x6c, 0xa6, 0xcb, 0xf8, 0x39, 0xe3, 0x66, 0x08, - 0xc2, 0x4d, 0x88, 0x72, 0x2d, 0x48, 0xe1, 0x34, 0xff, 0xd6, 0x1a, 0x91, 0x4d, 0x48, 0x38, 0x27, - 0x9e, 0x69, 0xf7, 0xd2, 0xe2, 0x8e, 0x58, 0x48, 0x62, 0xdc, 0x39, 0xf1, 0x94, 0x1e, 0xc9, 0xc3, - 0x7a, 0x87, 0x73, 0xcb, 0x33, 0xbb, 0x67, 0x1d, 0xce, 0x7d, 0x1c, 0x09, 0xf0, 0x5a, 0x50, 0xad, - 0xf9, 0x45, 0xa5, 0xf7, 0x3a, 0xf6, 0xed, 0xfb, 0xb6, 0x90, 0xfb, 0x22, 0xc2, 0xbd, 0xff, 0x2d, - 0xa9, 0xe3, 0xb9, 0x23, 0xf2, 0x02, 0xa2, 0xa7, 0xd6, 0x28, 0xe8, 0x98, 0x2a, 0x3f, 0x2e, 0x5e, - 0x3f, 0x7a, 0x71, 0x61, 0x0c, 0xf4, 0x7d, 0xf2, 0x06, 0xe2, 0x2e, 0x3b, 0xb3, 0x78, 0x3a, 0xb2, - 0x13, 0x2d, 0xa4, 0xca, 0xb9, 0x1b, 0x83, 0xbe, 0x14, 0x9c, 0x54, 0x8d, 0x5d, 0xfc, 0xde, 0x16, - 0x30, 0x8c, 0xe5, 0x3e, 0x03, 0xcc, 0x11, 0x79, 0x05, 0x31, 0xbf, 0x1c, 0x4c, 0xb1, 0x5e, 0xce, - 0xdf, 0x36, 0x85, 0x9f, 0xc4, 0x20, 0x41, 0x5e, 0x42, 0xb2, 0xd3, 0xeb, 0xb9, 0x16, 0xe7, 0xd3, - 0x59, 0x92, 0xd5, 0xf4, 0xcf, 0x1f, 0x7b, 0x1b, 0xd3, 0xff, 0x58, 0x09, 0x99, 0xee, 0xb9, 0xb6, - 0xd3, 0xc7, 0xb9, 0xfa, 0xf4, 0x6f, 0x04, 0xd6, 0x16, 0xdb, 0x91, 0x47, 0x90, 0x41, 0xda, 0x50, - 0x74, 0x03, 0x8f, 0x4c, 0xd4, 0x9a, 0xd4, 0x6c, 0xab, 0x7a, 0x8b, 0xd6, 0x94, 0x03, 0x85, 0xd6, - 0x25, 0x81, 0x64, 0x61, 0x6b, 0x19, 0xeb, 0x14, 0xdf, 0x29, 0x35, 0x8a, 0x92, 0xb8, 0x1a, 0xd5, - 0xdb, 0xd5, 0x19, 0x8e, 0x90, 0x87, 0x90, 0x5e, 0xc6, 0x35, 0x4d, 0x35, 0x50, 0x6b, 0x36, 0x29, - 0x4a, 0x51, 0xf2, 0x00, 0xee, 0x5f, 0xa1, 0x6d, 0xdd, 0xd0, 0xea, 0x4a, 0x45, 0x95, 0x62, 0xab, - 0xa7, 0x56, 0x35, 0x44, 0xed, 0x90, 0xa2, 0x14, 0x5f, 0x6d, 0xab, 0xa1, 0xd2, 0x50, 0xd4, 0x8a, - 0xa1, 0xa1, 0x94, 0x58, 0xa5, 0x4d, 0x85, 0xaa, 0xa6, 0x76, 0xa8, 0x52, 0x94, 0xee, 0x90, 0x02, - 0xe4, 0xaf, 0x7e, 0x4d, 0xad, 0x8d, 0xb4, 0x6e, 0xb6, 0x2a, 0x68, 0x1c, 0x99, 0x07, 0x1a, 0x06, - 0xbe, 0x74, 0x97, 0x3c, 0x81, 0xdd, 0xdb, 0x4c, 0xaa, 0x6a, 0x06, 0x95, 0x92, 0x24, 0x03, 0x9b, - 0xcb, 0x6a, 0xab, 0x49, 0xeb, 0x0d, 0x4a, 0x25, 0xa8, 0x9e, 0x5e, 0x8c, 0x65, 0xf1, 0x72, 0x2c, - 0x8b, 0x7f, 0xc6, 0xb2, 0xf8, 0x75, 0x22, 0x0b, 0x97, 0x13, 0x59, 0xf8, 0x35, 0x91, 0x05, 0xc8, - 0xd8, 0xec, 0x86, 0xdb, 0x6e, 0x89, 0xef, 0x9f, 0xf7, 0x6d, 0xef, 0xc3, 0xc7, 0xe3, 0x62, 0x97, - 0x9d, 0x97, 0xe6, 0xd2, 0x9e, 0xcd, 0x16, 0x76, 0xa5, 0x4f, 0xf3, 0xa7, 0xe8, 0x8d, 0x06, 0x16, - 0x3f, 0x4e, 0x04, 0x8f, 0xe7, 0xd9, 0xbf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x97, 0xb2, 0x95, 0xbf, - 0xae, 0x03, 0x00, 0x00, + // 678 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x4f, 0x4f, 0x13, 0x41, + 0x18, 0xc6, 0xbb, 0xdb, 0x3f, 0xd0, 0x17, 0x24, 0xeb, 0x08, 0xd8, 0x02, 0x16, 0xac, 0x90, 0x20, + 0x86, 0x36, 0x20, 0x1a, 0xe3, 0xc1, 0xa4, 0x7f, 0x86, 0xba, 0xa1, 0xd9, 0x69, 0xa6, 0x5b, 0x08, + 0x5e, 0x36, 0xa5, 0x3b, 0x94, 0x0d, 0x65, 0x67, 0xb3, 0xbb, 0x12, 0x7b, 0xd1, 0x93, 0x9e, 0x3d, + 0x7a, 0xf4, 0x43, 0xf8, 0x21, 0x38, 0x12, 0x4f, 0x9e, 0x8c, 0x81, 0xa3, 0x5f, 0xc2, 0xec, 0x2e, + 0x6d, 0x29, 0x2d, 0x20, 0xb7, 0xdd, 0xf7, 0xf9, 0x3d, 0xf3, 0xbe, 0xef, 0x93, 0xc9, 0xc0, 0x92, + 0x65, 0xf3, 0x63, 0x66, 0xd6, 0xcd, 0x06, 0xcb, 0xda, 0xac, 0x69, 0x38, 0xae, 0xdd, 0xce, 0x1e, + 0xaf, 0x75, 0xbf, 0x33, 0x96, 0xcd, 0x5d, 0x8e, 0xa6, 0x7b, 0x58, 0xa6, 0x2b, 0x1d, 0xaf, 0xcd, + 0x4c, 0x36, 0x79, 0x93, 0xfb, 0x48, 0xd6, 0xfb, 0x0a, 0xe8, 0x99, 0x64, 0x83, 0x3b, 0x47, 0xdc, + 0xd1, 0x02, 0x21, 0xf8, 0x09, 0xa4, 0x74, 0x05, 0xc6, 0xe8, 0x85, 0x7f, 0x8b, 0xb5, 0xd1, 0x14, + 0xc4, 0xcc, 0x7d, 0x57, 0x33, 0xf4, 0x84, 0xb0, 0x20, 0x2c, 0xc7, 0x69, 0xd4, 0xdc, 0x77, 0x65, + 0x1d, 0x2d, 0xc2, 0x44, 0xdd, 0x71, 0x98, 0xab, 0x35, 0x5a, 0x75, 0xc7, 0xf1, 0x64, 0xd1, 0x97, + 0xc7, 0xfd, 0x6a, 0xc1, 0x2b, 0xca, 0xfa, 0xeb, 0xc8, 0xb7, 0xef, 0xf3, 0xa1, 0xf4, 0x17, 0x01, + 0xee, 0x75, 0x8e, 0xc4, 0xa6, 0x6b, 0xb7, 0xd1, 0x0b, 0x08, 0x1f, 0xb2, 0xb6, 0x7f, 0xe2, 0xd8, + 0xfa, 0x93, 0xcc, 0xf0, 0xd1, 0x33, 0x97, 0xc6, 0xa0, 0x1e, 0x8f, 0xde, 0x40, 0xd4, 0xe6, 0x2d, + 0xe6, 0x24, 0xc4, 0x85, 0xf0, 0xf2, 0xd8, 0x7a, 0xfa, 0x5a, 0xa3, 0x07, 0xf9, 0x9d, 0xf2, 0x91, + 0x93, 0xdf, 0xf3, 0x21, 0x1a, 0xd8, 0xd2, 0x1f, 0x01, 0x7a, 0x12, 0x7a, 0x05, 0x11, 0xaf, 0xec, + 0x4f, 0x31, 0xb1, 0xbe, 0x78, 0xdb, 0x14, 0x9e, 0x93, 0xfa, 0x0e, 0xf4, 0x12, 0xe2, 0x75, 0x5d, + 0xb7, 0x99, 0xe3, 0x5c, 0xcc, 0x12, 0xcf, 0x27, 0x7e, 0xfe, 0x58, 0x9d, 0xbc, 0xc8, 0x31, 0x17, + 0x68, 0x55, 0xd7, 0x36, 0xcc, 0x26, 0xed, 0xa1, 0xe9, 0xcf, 0x61, 0xb8, 0x5f, 0x61, 0xa6, 0xee, + 0x95, 0x79, 0x8b, 0x15, 0x0e, 0xea, 0x66, 0x93, 0xa1, 0x09, 0x10, 0xbb, 0xe9, 0x8a, 0x86, 0xde, + 0x09, 0x47, 0xbc, 0x63, 0x38, 0x9d, 0x75, 0xc2, 0x77, 0x5e, 0x47, 0x86, 0x38, 0xb7, 0x98, 0x5d, + 0x77, 0x0d, 0x6e, 0x26, 0x22, 0xbe, 0xfd, 0xd9, 0x4d, 0xd1, 0x06, 0x73, 0x93, 0x8e, 0x85, 0xf6, + 0xdc, 0xfd, 0xc9, 0x44, 0xff, 0x3b, 0x19, 0xb4, 0x01, 0xa3, 0x96, 0xcd, 0x2d, 0xee, 0x30, 0x3b, + 0x11, 0xf3, 0x92, 0xb8, 0xc1, 0xd6, 0x25, 0xfd, 0x6e, 0x96, 0x37, 0x68, 0xbd, 0xe5, 0x24, 0x46, + 0x6e, 0xed, 0xd6, 0x41, 0x57, 0xfe, 0x8a, 0x30, 0x7e, 0x39, 0x07, 0xf4, 0x08, 0x92, 0x14, 0x97, + 0xe4, 0xaa, 0x4a, 0x77, 0x35, 0x4a, 0xca, 0x58, 0xab, 0x29, 0xd5, 0x0a, 0x2e, 0xc8, 0x9b, 0x32, + 0x2e, 0x4a, 0x21, 0x34, 0x03, 0xd3, 0xfd, 0x72, 0x15, 0xd3, 0x6d, 0xb9, 0x80, 0xa9, 0x24, 0x0c, + 0x5a, 0xab, 0xb5, 0x7c, 0x57, 0x16, 0xd1, 0x1c, 0x24, 0xfa, 0xe5, 0x02, 0x51, 0x54, 0x4a, 0xca, + 0x65, 0x4c, 0xa5, 0x30, 0x9a, 0x85, 0x87, 0x57, 0xd4, 0x5a, 0x55, 0x25, 0x45, 0x39, 0xa7, 0x48, + 0x91, 0xc1, 0xae, 0x79, 0x42, 0x29, 0xd9, 0xc1, 0x54, 0x8a, 0x0e, 0x1e, 0x4b, 0xa8, 0x5c, 0x92, + 0x95, 0x9c, 0x4a, 0xa8, 0x14, 0x1b, 0x54, 0xcb, 0x32, 0x56, 0x34, 0xb2, 0xa3, 0x60, 0x2a, 0x8d, + 0xa0, 0x65, 0x58, 0xbc, 0xba, 0x4d, 0xa1, 0x46, 0x71, 0x51, 0xab, 0xe4, 0xa8, 0xba, 0xab, 0x6d, + 0x12, 0xea, 0xf3, 0xd2, 0x28, 0x7a, 0x0a, 0x4b, 0xb7, 0x91, 0x58, 0x21, 0x2a, 0x96, 0xe2, 0x28, + 0x09, 0x53, 0xfd, 0x68, 0xa5, 0x8c, 0x8b, 0x25, 0x8c, 0x25, 0x58, 0xf9, 0x04, 0x0f, 0x86, 0xdc, + 0x1a, 0xb4, 0x04, 0x8f, 0x83, 0x95, 0xdf, 0xe6, 0x94, 0x12, 0xd6, 0x48, 0x05, 0xd3, 0x9c, 0x2a, + 0x13, 0xe5, 0x4a, 0xf6, 0xf3, 0x30, 0x3b, 0x1c, 0x2b, 0xd1, 0x9c, 0xa2, 0x4a, 0x02, 0x5a, 0x80, + 0xb9, 0xe1, 0x00, 0xc5, 0xdb, 0x64, 0x0b, 0x4b, 0x62, 0xfe, 0xf0, 0xe4, 0x2c, 0x25, 0x9c, 0x9e, + 0xa5, 0x84, 0x3f, 0x67, 0x29, 0xe1, 0xeb, 0x79, 0x2a, 0x74, 0x7a, 0x9e, 0x0a, 0xfd, 0x3a, 0x4f, + 0x85, 0x20, 0x69, 0xf0, 0x6b, 0x2e, 0x7a, 0x45, 0x78, 0xb7, 0xd1, 0x34, 0xdc, 0x83, 0xf7, 0x7b, + 0x99, 0x06, 0x3f, 0xca, 0xf6, 0xa0, 0x55, 0x83, 0x5f, 0xfa, 0xcb, 0x7e, 0xe8, 0xbd, 0xc9, 0x6e, + 0xdb, 0x62, 0xce, 0x5e, 0xcc, 0x7f, 0x45, 0x9f, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x97, 0xb8, + 0x7b, 0x80, 0xb7, 0x05, 0x00, 0x00, } func (m *RegistryKey) Marshal() (dAtA []byte, err error) { @@ -446,6 +592,83 @@ func (m *RolesEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *PendingRoleChange) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *PendingRoleChange) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PendingRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Approvals) > 0 { + for iNdEx := len(m.Approvals) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Approvals[iNdEx]) + copy(dAtA[i:], m.Approvals[iNdEx]) + i = encodeVarintRegistry(dAtA, i, uint64(len(m.Approvals[iNdEx]))) + i-- + dAtA[i] = 0x3a + } + } + if len(m.Proposer) > 0 { + i -= len(m.Proposer) + copy(dAtA[i:], m.Proposer) + i = encodeVarintRegistry(dAtA, i, uint64(len(m.Proposer))) + i-- + dAtA[i] = 0x32 + } + if len(m.Addresses) > 0 { + for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addresses[iNdEx]) + copy(dAtA[i:], m.Addresses[iNdEx]) + i = encodeVarintRegistry(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + if m.Operation != 0 { + i = encodeVarintRegistry(dAtA, i, uint64(m.Operation)) + i-- + dAtA[i] = 0x20 + } + if m.Role != 0 { + i = encodeVarintRegistry(dAtA, i, uint64(m.Role)) + i-- + dAtA[i] = 0x18 + } + if m.Key != nil { + { + size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintRegistry(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarintRegistry(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintRegistry(dAtA []byte, offset int, v uint64) int { offset -= sovRegistry(v) base := offset @@ -511,6 +734,45 @@ func (m *RolesEntry) Size() (n int) { return n } +func (m *PendingRoleChange) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovRegistry(uint64(l)) + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovRegistry(uint64(l)) + } + if m.Role != 0 { + n += 1 + sovRegistry(uint64(m.Role)) + } + if m.Operation != 0 { + n += 1 + sovRegistry(uint64(m.Operation)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { + l = len(s) + n += 1 + l + sovRegistry(uint64(l)) + } + } + l = len(m.Proposer) + if l > 0 { + n += 1 + l + sovRegistry(uint64(l)) + } + if len(m.Approvals) > 0 { + for _, s := range m.Approvals { + l = len(s) + n += 1 + l + sovRegistry(uint64(l)) + } + } + return n +} + func sovRegistry(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -852,6 +1114,258 @@ func (m *RolesEntry) Unmarshal(dAtA []byte) error { } return nil } +func (m *PendingRoleChange) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PendingRoleChange: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PendingRoleChange: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Key == nil { + m.Key = &RegistryKey{} + } + if err := m.Key.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + m.Role = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Role |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) + } + m.Operation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Operation |= RoleChangeOperation(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Proposer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Proposer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Approvals", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Approvals = append(m.Approvals, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRegistry(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRegistry + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipRegistry(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 3e3022ade2..2b51d6c136 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -726,6 +726,248 @@ func (m *MsgSetRolesResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSetRolesResponse proto.InternalMessageInfo +// MsgProposeRoleChange opens a pending role change that collects single-signer approvals until +// the role's authorization policy is satisfied. Using single-signer messages (rather than one +// multi-signer message) keeps every step compatible with authz delegation. +type MsgProposeRoleChange struct { + // signer is the address proposing the change. It is recorded as the first approval; it only + // counts toward the policy if the signer is itself a required party. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // key identifies the registry entry to change. + Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // role is the role being changed. + Role RegistryRole `protobuf:"varint,3,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` + // operation is the kind of change to apply once approved (grant or revoke). + Operation RoleChangeOperation `protobuf:"varint,4,opt,name=operation,proto3,enum=provenance.registry.v1.RoleChangeOperation" json:"operation,omitempty"` + // addresses is the set of addresses the operation applies to. + Addresses []string `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` +} + +func (m *MsgProposeRoleChange) Reset() { *m = MsgProposeRoleChange{} } +func (m *MsgProposeRoleChange) String() string { return proto.CompactTextString(m) } +func (*MsgProposeRoleChange) ProtoMessage() {} +func (*MsgProposeRoleChange) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{13} +} +func (m *MsgProposeRoleChange) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgProposeRoleChange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgProposeRoleChange.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgProposeRoleChange) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgProposeRoleChange.Merge(m, src) +} +func (m *MsgProposeRoleChange) XXX_Size() int { + return m.Size() +} +func (m *MsgProposeRoleChange) XXX_DiscardUnknown() { + xxx_messageInfo_MsgProposeRoleChange.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgProposeRoleChange proto.InternalMessageInfo + +func (m *MsgProposeRoleChange) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgProposeRoleChange) GetKey() *RegistryKey { + if m != nil { + return m.Key + } + return nil +} + +func (m *MsgProposeRoleChange) GetRole() RegistryRole { + if m != nil { + return m.Role + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *MsgProposeRoleChange) GetOperation() RoleChangeOperation { + if m != nil { + return m.Operation + } + return RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED +} + +func (m *MsgProposeRoleChange) GetAddresses() []string { + if m != nil { + return m.Addresses + } + return nil +} + +// MsgProposeRoleChangeResponse defines the response for ProposeRoleChange. +type MsgProposeRoleChangeResponse struct { + // change_id is the deterministic identifier of the pending change. It is also returned when the + // change auto-applied immediately (e.g. a single-party policy satisfied by the proposer). + ChangeId string `protobuf:"bytes,1,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` + // applied is true if the proposal alone satisfied the policy and the change was applied. + Applied bool `protobuf:"varint,2,opt,name=applied,proto3" json:"applied,omitempty"` +} + +func (m *MsgProposeRoleChangeResponse) Reset() { *m = MsgProposeRoleChangeResponse{} } +func (m *MsgProposeRoleChangeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgProposeRoleChangeResponse) ProtoMessage() {} +func (*MsgProposeRoleChangeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{14} +} +func (m *MsgProposeRoleChangeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgProposeRoleChangeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgProposeRoleChangeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgProposeRoleChangeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgProposeRoleChangeResponse.Merge(m, src) +} +func (m *MsgProposeRoleChangeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgProposeRoleChangeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgProposeRoleChangeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgProposeRoleChangeResponse proto.InternalMessageInfo + +func (m *MsgProposeRoleChangeResponse) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +func (m *MsgProposeRoleChangeResponse) GetApplied() bool { + if m != nil { + return m.Applied + } + return false +} + +// MsgApproveRoleChange records a single-signer approval for an open pending role change. +type MsgApproveRoleChange struct { + // signer is the approving party. Single-signer, so it can be delegated via authz. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // change_id is the identifier of the pending change to approve. + ChangeId string `protobuf:"bytes,2,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` +} + +func (m *MsgApproveRoleChange) Reset() { *m = MsgApproveRoleChange{} } +func (m *MsgApproveRoleChange) String() string { return proto.CompactTextString(m) } +func (*MsgApproveRoleChange) ProtoMessage() {} +func (*MsgApproveRoleChange) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{15} +} +func (m *MsgApproveRoleChange) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgApproveRoleChange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgApproveRoleChange.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgApproveRoleChange) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgApproveRoleChange.Merge(m, src) +} +func (m *MsgApproveRoleChange) XXX_Size() int { + return m.Size() +} +func (m *MsgApproveRoleChange) XXX_DiscardUnknown() { + xxx_messageInfo_MsgApproveRoleChange.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgApproveRoleChange proto.InternalMessageInfo + +func (m *MsgApproveRoleChange) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgApproveRoleChange) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +// MsgApproveRoleChangeResponse defines the response for ApproveRoleChange. +type MsgApproveRoleChangeResponse struct { + // applied is true if this approval satisfied the policy and the change was applied. + Applied bool `protobuf:"varint,1,opt,name=applied,proto3" json:"applied,omitempty"` +} + +func (m *MsgApproveRoleChangeResponse) Reset() { *m = MsgApproveRoleChangeResponse{} } +func (m *MsgApproveRoleChangeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgApproveRoleChangeResponse) ProtoMessage() {} +func (*MsgApproveRoleChangeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{16} +} +func (m *MsgApproveRoleChangeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgApproveRoleChangeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgApproveRoleChangeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgApproveRoleChangeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgApproveRoleChangeResponse.Merge(m, src) +} +func (m *MsgApproveRoleChangeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgApproveRoleChangeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgApproveRoleChangeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgApproveRoleChangeResponse proto.InternalMessageInfo + +func (m *MsgApproveRoleChangeResponse) GetApplied() bool { + if m != nil { + return m.Applied + } + return false +} + func init() { proto.RegisterType((*MsgRegisterNFT)(nil), "provenance.registry.v1.MsgRegisterNFT") proto.RegisterType((*MsgRegisterNFTResponse)(nil), "provenance.registry.v1.MsgRegisterNFTResponse") @@ -740,56 +982,70 @@ func init() { proto.RegisterType((*MsgSetRoles)(nil), "provenance.registry.v1.MsgSetRoles") proto.RegisterType((*RoleUpdate)(nil), "provenance.registry.v1.RoleUpdate") proto.RegisterType((*MsgSetRolesResponse)(nil), "provenance.registry.v1.MsgSetRolesResponse") + proto.RegisterType((*MsgProposeRoleChange)(nil), "provenance.registry.v1.MsgProposeRoleChange") + proto.RegisterType((*MsgProposeRoleChangeResponse)(nil), "provenance.registry.v1.MsgProposeRoleChangeResponse") + proto.RegisterType((*MsgApproveRoleChange)(nil), "provenance.registry.v1.MsgApproveRoleChange") + proto.RegisterType((*MsgApproveRoleChangeResponse)(nil), "provenance.registry.v1.MsgApproveRoleChangeResponse") } func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 694 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x96, 0xcf, 0x4f, 0x13, 0x41, - 0x14, 0xc7, 0x3b, 0xb4, 0x80, 0xbc, 0x02, 0x31, 0x2b, 0x3f, 0x96, 0x4d, 0x2c, 0x4d, 0x01, 0xd3, - 0xa0, 0xdd, 0x85, 0x2a, 0xc6, 0x70, 0x30, 0xb1, 0x09, 0x7a, 0x20, 0x35, 0x66, 0x91, 0x8b, 0x31, - 0x21, 0x05, 0x26, 0xc3, 0xa6, 0x74, 0xa7, 0x99, 0x99, 0x36, 0x94, 0x83, 0x31, 0x9e, 0x3c, 0xfa, - 0x0f, 0xf8, 0x3f, 0x70, 0xf0, 0x6f, 0x30, 0x1c, 0x89, 0x5e, 0x3c, 0x19, 0x03, 0x07, 0xfe, 0x06, - 0xbd, 0x68, 0xf6, 0xd7, 0xec, 0x2e, 0xa1, 0xed, 0x8a, 0x89, 0x1e, 0xbc, 0xed, 0xec, 0x7c, 0xde, - 0x7b, 0xdf, 0xf7, 0xf2, 0xde, 0xcb, 0xc0, 0x6c, 0x93, 0xd1, 0x36, 0xb6, 0x6b, 0xf6, 0x0e, 0x36, - 0x18, 0x26, 0x16, 0x17, 0xac, 0x63, 0xb4, 0x97, 0x0d, 0x71, 0xa0, 0x37, 0x19, 0x15, 0x54, 0x99, - 0x0a, 0x01, 0x3d, 0x00, 0xf4, 0xf6, 0xb2, 0x36, 0x41, 0x28, 0xa1, 0x2e, 0x62, 0x38, 0x5f, 0x1e, - 0xad, 0xcd, 0xec, 0x50, 0xde, 0xa0, 0x7c, 0xcb, 0xbb, 0xf0, 0x0e, 0xfe, 0xd5, 0xb4, 0x77, 0x32, - 0x1a, 0x9c, 0x38, 0x01, 0x1a, 0x9c, 0xf8, 0x17, 0x0b, 0x5d, 0x24, 0xc8, 0x68, 0x1e, 0xb6, 0xd8, - 0x05, 0xab, 0xb5, 0xc4, 0x1e, 0x65, 0xd6, 0x61, 0x4d, 0x58, 0xd4, 0xf6, 0xd8, 0xc2, 0x47, 0x04, - 0xe3, 0x55, 0x4e, 0x4c, 0x17, 0xc3, 0xec, 0xe9, 0xe3, 0xe7, 0xca, 0x12, 0x0c, 0x71, 0x8b, 0xd8, - 0x98, 0xa9, 0x28, 0x8f, 0x8a, 0x23, 0x15, 0xf5, 0xd3, 0x87, 0xd2, 0x84, 0x2f, 0xf0, 0xd1, 0xee, - 0x2e, 0xc3, 0x9c, 0x6f, 0x08, 0x66, 0xd9, 0xc4, 0xf4, 0x39, 0x65, 0x05, 0xd2, 0x75, 0xdc, 0x51, - 0x07, 0xf2, 0xa8, 0x98, 0x2d, 0xcf, 0xe9, 0x97, 0xd7, 0x41, 0x37, 0xfd, 0xef, 0x75, 0xdc, 0x31, - 0x1d, 0x5e, 0x79, 0x08, 0x83, 0x8c, 0xee, 0x63, 0xae, 0xa6, 0xf3, 0xe9, 0x62, 0xb6, 0x5c, 0xe8, - 0x6a, 0xe8, 0x40, 0x6b, 0xb6, 0x60, 0x9d, 0x4a, 0xe6, 0xf8, 0xeb, 0x6c, 0xca, 0xf4, 0xcc, 0x56, - 0xb3, 0x6f, 0xce, 0x8f, 0x16, 0x7d, 0x0d, 0x05, 0x15, 0xa6, 0xe2, 0x79, 0x98, 0x98, 0x37, 0xa9, - 0xcd, 0x71, 0xe1, 0x3b, 0x82, 0xd1, 0x2a, 0x27, 0x4f, 0x58, 0xcd, 0x16, 0x8e, 0x2b, 0xa5, 0x0c, - 0xc3, 0x9e, 0x11, 0x57, 0x51, 0x3e, 0xdd, 0x33, 0xc3, 0x00, 0xbc, 0x6a, 0x8a, 0x0f, 0x20, 0xe3, - 0x68, 0x55, 0xd3, 0x79, 0x54, 0x1c, 0x2f, 0xcf, 0xf7, 0xb3, 0x73, 0xe4, 0x99, 0xae, 0x85, 0x72, - 0x1f, 0x46, 0x6a, 0x9e, 0x14, 0xcc, 0xd5, 0x4c, 0x1f, 0x99, 0x21, 0xba, 0x3a, 0xea, 0x14, 0x25, - 0x90, 0x5d, 0x98, 0x82, 0x89, 0x68, 0xea, 0xb2, 0x26, 0x3f, 0x10, 0x8c, 0xb9, 0xe5, 0x6a, 0xd3, - 0x3a, 0xfe, 0xdf, 0x8a, 0x32, 0x0d, 0x93, 0xb1, 0xdc, 0x65, 0x55, 0xde, 0x22, 0xb8, 0x5e, 0xe5, - 0x64, 0xd3, 0x66, 0xff, 0x60, 0x1c, 0xe2, 0xed, 0xac, 0x81, 0x7a, 0x51, 0x89, 0x94, 0xf9, 0x1e, - 0xf9, 0x09, 0x78, 0x0e, 0x2a, 0xad, 0xfd, 0xfa, 0x66, 0x73, 0xb7, 0x26, 0xf0, 0x15, 0xb4, 0xae, - 0xc1, 0x30, 0xb6, 0x05, 0xb3, 0x30, 0x57, 0x07, 0xdc, 0x29, 0x5c, 0xe8, 0xa7, 0x37, 0x3a, 0x88, - 0x81, 0x6d, 0x5c, 0xfb, 0x2c, 0xdc, 0xbc, 0x54, 0x9e, 0x4c, 0xe0, 0x33, 0x82, 0x6c, 0x95, 0x93, - 0x0d, 0xec, 0x36, 0x25, 0xff, 0x9b, 0xbd, 0xb7, 0x0e, 0xa3, 0x4e, 0x27, 0x6d, 0xb5, 0x5c, 0x45, - 0x89, 0x56, 0x8f, 0x27, 0xde, 0xcf, 0x38, 0xcb, 0xe4, 0x9f, 0x8b, 0x6d, 0xf5, 0x0a, 0x20, 0xc4, - 0x65, 0x93, 0xa3, 0x3f, 0x6b, 0xf2, 0x81, 0xc4, 0x4d, 0x5e, 0x98, 0x84, 0x1b, 0x91, 0xa2, 0x06, - 0xc5, 0x2e, 0xff, 0xcc, 0x40, 0xba, 0xca, 0x89, 0x82, 0x21, 0x1b, 0xdd, 0xf2, 0xb7, 0xba, 0x29, - 0x8a, 0x6f, 0x51, 0x4d, 0x4f, 0xc6, 0x05, 0xe1, 0x94, 0x2d, 0x18, 0x09, 0x37, 0xed, 0x7c, 0x0f, - 0x63, 0x49, 0x69, 0x77, 0x92, 0x50, 0x32, 0xc0, 0x36, 0x40, 0x64, 0x6d, 0x2d, 0xf4, 0x94, 0x17, - 0x60, 0x5a, 0x29, 0x11, 0x26, 0x63, 0xd4, 0x61, 0x2c, 0xbe, 0x04, 0x8a, 0x3d, 0xec, 0x63, 0xa4, - 0xb6, 0x94, 0x94, 0x94, 0xc1, 0x0e, 0x41, 0xb9, 0x64, 0x94, 0x4b, 0x7d, 0xeb, 0x1e, 0xc5, 0xb5, - 0x95, 0xdf, 0xc2, 0x65, 0xec, 0x97, 0x70, 0x4d, 0x4e, 0xe1, 0x5c, 0x0f, 0x17, 0x01, 0xa4, 0xdd, - 0x4e, 0x00, 0x05, 0xde, 0xb5, 0xc1, 0xd7, 0xe7, 0x47, 0x8b, 0xa8, 0x52, 0x3f, 0x3e, 0xcd, 0xa1, - 0x93, 0xd3, 0x1c, 0xfa, 0x76, 0x9a, 0x43, 0xef, 0xce, 0x72, 0xa9, 0x93, 0xb3, 0x5c, 0xea, 0xcb, - 0x59, 0x2e, 0x05, 0x33, 0x16, 0xed, 0xe2, 0xef, 0x19, 0x7a, 0x71, 0x8f, 0x58, 0x62, 0xaf, 0xb5, - 0xad, 0xef, 0xd0, 0x86, 0x11, 0x42, 0x25, 0x8b, 0x46, 0x4e, 0xc6, 0x41, 0xf8, 0xc2, 0x11, 0x9d, - 0x26, 0xe6, 0xdb, 0x43, 0xee, 0xbb, 0xe6, 0xee, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x99, 0xf7, - 0x2e, 0x80, 0xaf, 0x09, 0x00, 0x00, + // 850 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0xcf, 0x6e, 0xeb, 0x44, + 0x14, 0xc6, 0x33, 0x49, 0xfa, 0x27, 0x27, 0xbd, 0x57, 0x60, 0x7a, 0x7b, 0x7d, 0x0d, 0xa4, 0x51, + 0x6e, 0x8b, 0xa2, 0x96, 0x24, 0x6d, 0x68, 0x51, 0xd5, 0x05, 0x52, 0x83, 0x0a, 0xaa, 0xaa, 0x40, + 0xe5, 0xd2, 0x0d, 0x42, 0x8a, 0xdc, 0x64, 0xe4, 0x5a, 0x69, 0x3c, 0xd6, 0x8c, 0x13, 0x9a, 0x2e, + 0x10, 0x62, 0xc5, 0x92, 0x17, 0xe0, 0x1d, 0xba, 0xe0, 0x19, 0x50, 0xd9, 0x55, 0xb0, 0x61, 0x85, + 0x50, 0xbb, 0xe8, 0x9a, 0x25, 0xac, 0x90, 0xc7, 0xf6, 0xc4, 0x6e, 0x12, 0xc7, 0x6d, 0xa5, 0xb2, + 0x60, 0x97, 0xb1, 0x7f, 0x67, 0xce, 0x77, 0xbe, 0x39, 0x33, 0x13, 0xc3, 0xa2, 0x45, 0x49, 0x0f, + 0x9b, 0x9a, 0xd9, 0xc4, 0x15, 0x8a, 0x75, 0x83, 0xd9, 0xb4, 0x5f, 0xe9, 0xad, 0x57, 0xec, 0xb3, + 0xb2, 0x45, 0x89, 0x4d, 0xa4, 0x85, 0x01, 0x50, 0xf6, 0x81, 0x72, 0x6f, 0x5d, 0x99, 0xd7, 0x89, + 0x4e, 0x38, 0x52, 0x71, 0x7e, 0xb9, 0xb4, 0xf2, 0xaa, 0x49, 0x58, 0x87, 0xb0, 0x86, 0xfb, 0xc2, + 0x1d, 0x78, 0xaf, 0x5e, 0xba, 0xa3, 0x4a, 0x87, 0xe9, 0x4e, 0x82, 0x0e, 0xd3, 0xbd, 0x17, 0xcb, + 0x63, 0x24, 0x88, 0x6c, 0x2e, 0xb6, 0x32, 0x06, 0xd3, 0xba, 0xf6, 0x09, 0xa1, 0xc6, 0xb9, 0x66, + 0x1b, 0xc4, 0x74, 0xd9, 0xc2, 0xcf, 0x08, 0x9e, 0xd7, 0x99, 0xae, 0x72, 0x0c, 0xd3, 0xcf, 0x3e, + 0xf9, 0x42, 0x5a, 0x83, 0x69, 0x66, 0xe8, 0x26, 0xa6, 0x32, 0xca, 0xa3, 0x62, 0xa6, 0x26, 0xff, + 0xfa, 0x53, 0x69, 0xde, 0x13, 0xb8, 0xd3, 0x6a, 0x51, 0xcc, 0xd8, 0xa1, 0x4d, 0x0d, 0x53, 0x57, + 0x3d, 0x4e, 0xda, 0x84, 0x54, 0x1b, 0xf7, 0xe5, 0x64, 0x1e, 0x15, 0xb3, 0xd5, 0xd7, 0xe5, 0xd1, + 0x3e, 0x94, 0x55, 0xef, 0xf7, 0x3e, 0xee, 0xab, 0x0e, 0x2f, 0x7d, 0x04, 0x53, 0x94, 0x9c, 0x62, + 0x26, 0xa7, 0xf2, 0xa9, 0x62, 0xb6, 0x5a, 0x18, 0x1b, 0xe8, 0x40, 0xbb, 0xa6, 0x4d, 0xfb, 0xb5, + 0xf4, 0xe5, 0x1f, 0x8b, 0x09, 0xd5, 0x0d, 0xdb, 0xce, 0x7e, 0x77, 0x7b, 0xb1, 0xe2, 0x69, 0x28, + 0xc8, 0xb0, 0x10, 0xae, 0x43, 0xc5, 0xcc, 0x22, 0x26, 0xc3, 0x85, 0xbf, 0x11, 0xcc, 0xd5, 0x99, + 0xfe, 0x29, 0xd5, 0x4c, 0xdb, 0x99, 0x4a, 0xaa, 0xc2, 0x8c, 0x1b, 0xc4, 0x64, 0x94, 0x4f, 0x45, + 0x56, 0xe8, 0x83, 0x0f, 0x2d, 0x71, 0x0b, 0xd2, 0x8e, 0x56, 0x39, 0x95, 0x47, 0xc5, 0xe7, 0xd5, + 0xa5, 0x49, 0x71, 0x8e, 0x3c, 0x95, 0x47, 0x48, 0x1f, 0x42, 0x46, 0x73, 0xa5, 0x60, 0x26, 0xa7, + 0x27, 0xc8, 0x1c, 0xa0, 0xdb, 0x73, 0x8e, 0x29, 0xbe, 0xec, 0xc2, 0x02, 0xcc, 0x07, 0x4b, 0x17, + 0x9e, 0xfc, 0x83, 0xe0, 0x19, 0xb7, 0xab, 0x47, 0xda, 0xf8, 0xff, 0x66, 0xca, 0x4b, 0x78, 0x11, + 0xaa, 0x5d, 0xb8, 0xf2, 0x3d, 0x82, 0x37, 0xea, 0x4c, 0x3f, 0x32, 0xe9, 0x7f, 0xb0, 0x1d, 0xc2, + 0xed, 0xac, 0x80, 0x7c, 0x57, 0x89, 0x90, 0xf9, 0x23, 0xf2, 0x0a, 0x70, 0x27, 0xa8, 0x75, 0x4f, + 0xdb, 0x47, 0x56, 0x4b, 0xb3, 0xf1, 0x03, 0xb4, 0xee, 0xc2, 0x0c, 0x36, 0x6d, 0x6a, 0x60, 0x26, + 0x27, 0xf9, 0x2e, 0x5c, 0x9e, 0xa4, 0x37, 0xb8, 0x11, 0xfd, 0xd8, 0xb0, 0xf6, 0x45, 0x78, 0x77, + 0xa4, 0x3c, 0x51, 0xc0, 0x6f, 0x08, 0xb2, 0x75, 0xa6, 0x1f, 0x62, 0xde, 0x94, 0xec, 0x29, 0x7b, + 0x6f, 0x1f, 0xe6, 0x9c, 0x4e, 0x6a, 0x74, 0xb9, 0xa2, 0x58, 0x47, 0x8f, 0x2b, 0xde, 0xab, 0x38, + 0x4b, 0xc5, 0x93, 0xbb, 0x6d, 0xf5, 0x0d, 0xc0, 0x00, 0x17, 0x4d, 0x8e, 0x1e, 0xd7, 0xe4, 0xc9, + 0xd8, 0x4d, 0x5e, 0x78, 0x01, 0x6f, 0x05, 0x4c, 0x15, 0x66, 0xff, 0x92, 0xe4, 0x67, 0xc0, 0x01, + 0x25, 0x16, 0x61, 0xbc, 0xdf, 0x3f, 0x3e, 0xd1, 0x4c, 0x1d, 0x3f, 0xdd, 0x39, 0xff, 0xf0, 0xfd, + 0xbe, 0x07, 0x19, 0x62, 0x61, 0xca, 0x2f, 0x2c, 0x39, 0xcd, 0xc3, 0x57, 0xa3, 0x96, 0xca, 0xad, + 0xec, 0x73, 0x3f, 0x44, 0x1d, 0x44, 0x87, 0x5d, 0x9d, 0x8a, 0x7f, 0x74, 0x84, 0x3a, 0xfb, 0x08, + 0xde, 0x19, 0x65, 0xa5, 0xef, 0xb5, 0xf4, 0x36, 0x64, 0x9a, 0xfc, 0x49, 0xc3, 0x68, 0xb9, 0xae, + 0xaa, 0xb3, 0xee, 0x83, 0xbd, 0x96, 0x24, 0xc3, 0x8c, 0x66, 0x59, 0xa7, 0x06, 0x6e, 0x71, 0x07, + 0x67, 0x55, 0x7f, 0x58, 0xa0, 0x7c, 0x85, 0x76, 0x2c, 0x5e, 0xd9, 0xa3, 0x56, 0x28, 0x24, 0x20, + 0x19, 0x16, 0x10, 0x2e, 0x65, 0x8b, 0x97, 0x32, 0x94, 0x53, 0x94, 0x12, 0x50, 0x8b, 0x42, 0x6a, + 0xab, 0x7f, 0x4d, 0x43, 0xaa, 0xce, 0x74, 0x09, 0x43, 0x36, 0xf8, 0xb7, 0xe1, 0xbd, 0x71, 0x0b, + 0x13, 0xbe, 0x96, 0x95, 0x72, 0x3c, 0x4e, 0x08, 0x69, 0x40, 0x66, 0x70, 0x75, 0x2f, 0x45, 0x04, + 0x0b, 0x4a, 0x79, 0x3f, 0x0e, 0x25, 0x12, 0x1c, 0x03, 0x04, 0xee, 0xc1, 0xe5, 0x48, 0x79, 0x3e, + 0xa6, 0x94, 0x62, 0x61, 0x22, 0x47, 0x1b, 0x9e, 0x85, 0x6f, 0x95, 0x62, 0x44, 0x7c, 0x88, 0x54, + 0xd6, 0xe2, 0x92, 0x22, 0xd9, 0x39, 0x48, 0x23, 0xee, 0x86, 0xd2, 0x44, 0xdf, 0x83, 0xb8, 0xb2, + 0x79, 0x2f, 0x5c, 0xe4, 0xfe, 0x0a, 0x66, 0xc5, 0xb1, 0xfe, 0x3a, 0x62, 0x0a, 0x1f, 0x52, 0x56, + 0x63, 0x40, 0x62, 0xf6, 0xaf, 0xe1, 0xcd, 0xe1, 0x73, 0x2c, 0x6a, 0xb5, 0x87, 0x68, 0x65, 0xe3, + 0x3e, 0x74, 0x30, 0xf1, 0xf0, 0xf6, 0x8c, 0x4a, 0x3c, 0x44, 0x47, 0x26, 0x1e, 0xbb, 0x0d, 0x95, + 0xa9, 0x6f, 0x6f, 0x2f, 0x56, 0x50, 0xad, 0x7d, 0x79, 0x9d, 0x43, 0x57, 0xd7, 0x39, 0xf4, 0xe7, + 0x75, 0x0e, 0xfd, 0x70, 0x93, 0x4b, 0x5c, 0xdd, 0xe4, 0x12, 0xbf, 0xdf, 0xe4, 0x12, 0xf0, 0xca, + 0x20, 0x63, 0x26, 0x3e, 0x40, 0x5f, 0x6e, 0xe8, 0x86, 0x7d, 0xd2, 0x3d, 0x2e, 0x37, 0x49, 0xa7, + 0x32, 0x80, 0x4a, 0x06, 0x09, 0x8c, 0x2a, 0x67, 0x83, 0x8f, 0x04, 0xbb, 0x6f, 0x61, 0x76, 0x3c, + 0xcd, 0x3f, 0x0d, 0x3e, 0xf8, 0x37, 0x00, 0x00, 0xff, 0xff, 0x36, 0x32, 0x5f, 0x21, 0xf2, 0x0c, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -825,6 +1081,12 @@ type MsgClient interface { // SetRoles atomically sets the desired state for one or more roles on a registry entry. // Multiple signers are supported for multi-party authorization workflows. SetRoles(ctx context.Context, in *MsgSetRoles, opts ...grpc.CallOption) (*MsgSetRolesResponse, error) + // ProposeRoleChange opens a pending role change that accumulates single-signer approvals. + // The change auto-applies once the role's authorization policy is satisfied. + ProposeRoleChange(ctx context.Context, in *MsgProposeRoleChange, opts ...grpc.CallOption) (*MsgProposeRoleChangeResponse, error) + // ApproveRoleChange records a single-signer approval for an open pending role change. + // When the accumulated approvals satisfy the role's policy, the change is applied automatically. + ApproveRoleChange(ctx context.Context, in *MsgApproveRoleChange, opts ...grpc.CallOption) (*MsgApproveRoleChangeResponse, error) } type msgClient struct { @@ -889,6 +1151,24 @@ func (c *msgClient) SetRoles(ctx context.Context, in *MsgSetRoles, opts ...grpc. return out, nil } +func (c *msgClient) ProposeRoleChange(ctx context.Context, in *MsgProposeRoleChange, opts ...grpc.CallOption) (*MsgProposeRoleChangeResponse, error) { + out := new(MsgProposeRoleChangeResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/ProposeRoleChange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) ApproveRoleChange(ctx context.Context, in *MsgApproveRoleChange, opts ...grpc.CallOption) (*MsgApproveRoleChangeResponse, error) { + out := new(MsgApproveRoleChangeResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/ApproveRoleChange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // RegisterNFT registers a new NFT in the registry. @@ -912,6 +1192,12 @@ type MsgServer interface { // SetRoles atomically sets the desired state for one or more roles on a registry entry. // Multiple signers are supported for multi-party authorization workflows. SetRoles(context.Context, *MsgSetRoles) (*MsgSetRolesResponse, error) + // ProposeRoleChange opens a pending role change that accumulates single-signer approvals. + // The change auto-applies once the role's authorization policy is satisfied. + ProposeRoleChange(context.Context, *MsgProposeRoleChange) (*MsgProposeRoleChangeResponse, error) + // ApproveRoleChange records a single-signer approval for an open pending role change. + // When the accumulated approvals satisfy the role's policy, the change is applied automatically. + ApproveRoleChange(context.Context, *MsgApproveRoleChange) (*MsgApproveRoleChangeResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -936,6 +1222,12 @@ func (*UnimplementedMsgServer) RegistryBulkUpdate(ctx context.Context, req *MsgR func (*UnimplementedMsgServer) SetRoles(ctx context.Context, req *MsgSetRoles) (*MsgSetRolesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetRoles not implemented") } +func (*UnimplementedMsgServer) ProposeRoleChange(ctx context.Context, req *MsgProposeRoleChange) (*MsgProposeRoleChangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ProposeRoleChange not implemented") +} +func (*UnimplementedMsgServer) ApproveRoleChange(ctx context.Context, req *MsgApproveRoleChange) (*MsgApproveRoleChangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ApproveRoleChange not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -1049,6 +1341,42 @@ func _Msg_SetRoles_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Msg_ProposeRoleChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgProposeRoleChange) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ProposeRoleChange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/ProposeRoleChange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ProposeRoleChange(ctx, req.(*MsgProposeRoleChange)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_ApproveRoleChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgApproveRoleChange) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).ApproveRoleChange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/ApproveRoleChange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).ApproveRoleChange(ctx, req.(*MsgApproveRoleChange)) + } + return interceptor(ctx, in, info, handler) +} + var Msg_serviceDesc = _Msg_serviceDesc var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Msg", @@ -1078,6 +1406,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "SetRoles", Handler: _Msg_SetRoles_Handler, }, + { + MethodName: "ProposeRoleChange", + Handler: _Msg_ProposeRoleChange_Handler, + }, + { + MethodName: "ApproveRoleChange", + Handler: _Msg_ApproveRoleChange_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/tx.proto", @@ -1574,70 +1910,241 @@ func (m *MsgSetRolesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - dAtA[offset] = uint8(v) - return base -} -func (m *MsgRegisterNFT) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) - } - if m.Key != nil { - l = m.Key.Size() - n += 1 + l + sovTx(uint64(l)) - } - if len(m.Roles) > 0 { - for _, e := range m.Roles { - l = e.Size() - n += 1 + l + sovTx(uint64(l)) - } +func (m *MsgProposeRoleChange) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - return n + return dAtA[:n], nil } -func (m *MsgRegisterNFTResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - return n +func (m *MsgProposeRoleChange) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *MsgGrantRole) Size() (n int) { - if m == nil { - return 0 - } +func (m *MsgProposeRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Signers) > 0 { - for _, s := range m.Signers { - l = len(s) - n += 1 + l + sovTx(uint64(l)) + if len(m.Addresses) > 0 { + for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addresses[iNdEx]) + copy(dAtA[i:], m.Addresses[iNdEx]) + i = encodeVarintTx(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + i-- + dAtA[i] = 0x2a } } - if m.Key != nil { - l = m.Key.Size() - n += 1 + l + sovTx(uint64(l)) + if m.Operation != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.Operation)) + i-- + dAtA[i] = 0x20 } if m.Role != 0 { - n += 1 + sovTx(uint64(m.Role)) + i = encodeVarintTx(dAtA, i, uint64(m.Role)) + i-- + dAtA[i] = 0x18 } - if len(m.Addresses) > 0 { - for _, s := range m.Addresses { + if m.Key != nil { + { + size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgProposeRoleChangeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgProposeRoleChangeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgProposeRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Applied { + i-- + if m.Applied { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgApproveRoleChange) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgApproveRoleChange) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgApproveRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgApproveRoleChangeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgApproveRoleChangeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgApproveRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Applied { + i-- + if m.Applied { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgRegisterNFT) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovTx(uint64(l)) + } + if len(m.Roles) > 0 { + for _, e := range m.Roles { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgRegisterNFTResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgGrantRole) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Signers) > 0 { + for _, s := range m.Signers { + l = len(s) + n += 1 + l + sovTx(uint64(l)) + } + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Role != 0 { + n += 1 + sovTx(uint64(m.Role)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { l = len(s) n += 1 + l + sovTx(uint64(l)) } @@ -1797,6 +2304,80 @@ func (m *MsgSetRolesResponse) Size() (n int) { return n } +func (m *MsgProposeRoleChange) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovTx(uint64(l)) + } + if m.Role != 0 { + n += 1 + sovTx(uint64(m.Role)) + } + if m.Operation != 0 { + n += 1 + sovTx(uint64(m.Operation)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { + l = len(s) + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgProposeRoleChangeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Applied { + n += 2 + } + return n +} + +func (m *MsgApproveRoleChange) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgApproveRoleChangeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Applied { + n += 2 + } + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3080,6 +3661,480 @@ func (m *MsgSetRolesResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgProposeRoleChange) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgProposeRoleChange: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgProposeRoleChange: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Key == nil { + m.Key = &RegistryKey{} + } + if err := m.Key.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + m.Role = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Role |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) + } + m.Operation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Operation |= RoleChangeOperation(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgProposeRoleChangeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgProposeRoleChangeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgProposeRoleChangeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Applied", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Applied = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgApproveRoleChange) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgApproveRoleChange: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgApproveRoleChange: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgApproveRoleChangeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgApproveRoleChangeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgApproveRoleChangeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Applied", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Applied = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 6fd8f87cd76690be724f60eb13ccd2922bf55150 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 18 Jun 2026 15:02:42 -0600 Subject: [PATCH 04/36] Revert registry role messages to single signer Revert MsgGrantRole/MsgRevokeRole/MsgSetRoles from repeated signers back to a single signer field so all registry messages are single-signer and therefore authz-delegable. Multi-party Controller updates now flow exclusively through the Option B propose/approve accumulation path. - tx.proto: 3 messages use single signer (regenerated tx.pb.go) - keeper/msg_server.go: handlers pass []string{msg.Signer} to policy engine - client/cli/tx.go: CLI uses Signer - types/msgs.go: single-signer ValidateBasic - types/msgs_test.go: makers and ValidateBasic cases updated - Delete authorization_acceptance_test.go (multi-signer model removed); Controller coverage lives in role_change_accumulation_acceptance_test.go Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/tx.proto | 40 +-- x/registry/client/cli/tx.go | 4 +- .../keeper/authorization_acceptance_test.go | 305 ------------------ x/registry/keeper/msg_server.go | 36 +-- ...ole_change_accumulation_acceptance_test.go | 6 + x/registry/types/msgs.go | 30 +- x/registry/types/msgs_test.go | 53 ++- x/registry/types/tx.pb.go | 247 +++++++------- 8 files changed, 195 insertions(+), 526 deletions(-) delete mode 100644 x/registry/keeper/authorization_acceptance_test.go diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index 9581ca73fe..fd61d4522f 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -22,12 +22,14 @@ service Msg { // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy the role's authorization policy directly; multi-party role + // changes are made through ProposeRoleChange / ApproveRoleChange. rpc GrantRole(MsgGrantRole) returns (MsgGrantRoleResponse); // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy the role's authorization policy directly; multi-party role + // changes are made through ProposeRoleChange / ApproveRoleChange. rpc RevokeRole(MsgRevokeRole) returns (MsgRevokeRoleResponse); // UnregisterNFT unregisters an NFT from the registry. @@ -40,7 +42,8 @@ service Msg { rpc RegistryBulkUpdate(MsgRegistryBulkUpdate) returns (MsgRegistryBulkUpdateResponse); // SetRoles atomically sets the desired state for one or more roles on a registry entry. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy each affected role's authorization policy directly; multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange. rpc SetRoles(MsgSetRoles) returns (MsgSetRolesResponse); // ProposeRoleChange opens a pending role change that accumulates single-signer approvals. @@ -76,14 +79,13 @@ message MsgRegisterNFTResponse {} // MsgGrantRole represents a message to grant a role to one or more addresses. // This message adds the specified addresses to an existing role for the given registry key. -// Multiple signers enable multi-party authorization workflows. message MsgGrantRole { - option (cosmos.msg.v1.signer) = "signers"; + option (cosmos.msg.v1.signer) = "signer"; - // signers are the addresses authorizing the role grant. - // When a RegistryClass authorization policy is defined for the target role, all required - // role holders must be included here. Without a policy, a single NFT owner address is required. - repeated string signers = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // signer is the address authorizing the role grant. The signer must satisfy the role's + // authorization policy on its own (e.g. the NFT owner for roles without a policy). Multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange instead. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // key is the registry key to grant the role to. // This identifies the specific registry entry to modify. @@ -104,14 +106,13 @@ message MsgGrantRoleResponse {} // MsgRevokeRole represents a message to revoke a role from one or more addresses. // This message removes the specified addresses from an existing role for the given registry key. -// Multiple signers enable multi-party authorization workflows. message MsgRevokeRole { - option (cosmos.msg.v1.signer) = "signers"; + option (cosmos.msg.v1.signer) = "signer"; - // signers are the addresses authorizing the role revocation. - // When a RegistryClass authorization policy is defined for the target role, all required - // role holders must be included here. Without a policy, a single NFT owner address is required. - repeated string signers = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // signer is the address authorizing the role revocation. The signer must satisfy the role's + // authorization policy on its own (e.g. the NFT owner for roles without a policy). Multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange instead. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // key is the registry key to revoke the role from. // This identifies the specific registry entry to modify. @@ -171,11 +172,12 @@ message MsgRegistryBulkUpdateResponse {} // addresses and applies grants and revokes to reach the desired state. // All updates succeed or all fail (atomic). message MsgSetRoles { - option (cosmos.msg.v1.signer) = "signers"; + option (cosmos.msg.v1.signer) = "signer"; - // signers are the addresses authorizing the role updates. - // All required role holders per the RegistryClass authorization policy must be present. - repeated string signers = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // signer is the address authorizing the role updates. The signer must satisfy each affected + // role's authorization policy on its own. Multi-party role changes are made through + // ProposeRoleChange / ApproveRoleChange instead. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // key identifies the registry entry to update. RegistryKey key = 2; diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index c28ac25a4e..dd522d0189 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -83,7 +83,7 @@ func CmdGrantRole() *cobra.Command { } msg := types.MsgGrantRole{ - Signers: []string{clientCtx.GetFromAddress().String()}, + Signer: clientCtx.GetFromAddress().String(), Key: &types.RegistryKey{ AssetClassId: args[0], NftId: args[1], @@ -120,7 +120,7 @@ func CmdRevokeRole() *cobra.Command { } msg := types.MsgRevokeRole{ - Signers: []string{clientCtx.GetFromAddress().String()}, + Signer: clientCtx.GetFromAddress().String(), Key: &types.RegistryKey{ AssetClassId: args[0], NftId: args[1], diff --git a/x/registry/keeper/authorization_acceptance_test.go b/x/registry/keeper/authorization_acceptance_test.go deleted file mode 100644 index 10a9b49a1e..0000000000 --- a/x/registry/keeper/authorization_acceptance_test.go +++ /dev/null @@ -1,305 +0,0 @@ -package keeper_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/suite" - - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" - - "cosmossdk.io/x/nft" - nftkeeper "cosmossdk.io/x/nft/keeper" - - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/authz" - - "github.com/provenance-io/provenance/app" - "github.com/provenance-io/provenance/x/registry/keeper" - "github.com/provenance-io/provenance/x/registry/types" -) - -// ControllerAuthAcceptanceTestSuite implements the acceptance tests from the ticket -// (sc-512248) that verify the static multi-signer authorization rules for updating -// the CONTROLLER role. -// -// Required signatures for a Controller update: -// - Current Controller -// - Current Secured Party for eNote (only if set) -// - New Controller -type ControllerAuthAcceptanceTestSuite struct { - suite.Suite - - app *app.App - ctx sdk.Context - - registryKeeper keeper.Keeper - nftKeeper nftkeeper.Keeper - msgServer types.MsgServer - - nftClass nft.Class - - // nftOwner owns the NFT and is the fallback authority when no controller is set. - nftOwner string - - currentController string - newController string - securedParty string - stranger string -} - -func TestControllerAuthAcceptanceTestSuite(t *testing.T) { - suite.Run(t, new(ControllerAuthAcceptanceTestSuite)) -} - -func (s *ControllerAuthAcceptanceTestSuite) SetupTest() { - s.app = app.Setup(s.T()) - s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) - - s.nftKeeper = s.app.NFTKeeper - s.registryKeeper = s.app.RegistryKeeper - s.msgServer = keeper.NewMsgServer(s.registryKeeper) - - s.nftOwner = genAddr() - s.currentController = genAddr() - s.newController = genAddr() - s.securedParty = genAddr() - s.stranger = genAddr() - - s.nftClass = nft.Class{Id: "test-nft-class-id"} - s.nftKeeper.SaveClass(s.ctx, s.nftClass) -} - -// genAddr generates a fresh bech32 account address. -func genAddr() string { - return sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() -} - -// mintNFT mints an NFT with the given id owned by nftOwner and returns its registry key. -func (s *ControllerAuthAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { - n := nft.NFT{ClassId: s.nftClass.Id, Id: id} - ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) - s.Require().NoError(err) - s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) - return &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} -} - -// setupRegistry creates a registry entry for the key with the provided roles. -func (s *ControllerAuthAcceptanceTestSuite) setupRegistry(key *types.RegistryKey, roles []types.RolesEntry) { - s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles)) -} - -// grantController attempts to grant the CONTROLLER role to newController, signed by the -// provided signers, and returns the resulting error (nil on success). -func (s *ControllerAuthAcceptanceTestSuite) grantController(key *types.RegistryKey, signers []string) error { - _, err := s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ - Signers: signers, - Key: key, - Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, - Addresses: []string{s.newController}, - }) - return err -} - -// roleAddresses returns the addresses currently assigned to the given role on the entry. -func (s *ControllerAuthAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { - entry, err := s.registryKeeper.GetRegistry(s.ctx, key) - s.Require().NoError(err) - s.Require().NotNil(entry) - for _, re := range entry.Roles { - if re.Role == role { - return re.Addresses - } - } - return nil -} - -// --- Scenario A: Controller set, no Secured Party for eNote --------------------------- - -func (s *ControllerAuthAcceptanceTestSuite) TestControllerSet_NoSecuredParty() { - controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER - - baseRoles := func() []types.RolesEntry { - return []types.RolesEntry{ - {Role: controllerRole, Addresses: []string{s.currentController}}, - } - } - - s.Run("happy path: current controller and new controller sign", func() { - key := s.mintNFT("nft-a-happy") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.currentController, s.newController}) - s.Require().NoError(err, "current + new controller should authorize the update") - - s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController, - "new controller should be added to the CONTROLLER role") - }) - - s.Run("reject: only current controller signs", func() { - key := s.mintNFT("nft-a-cc-only") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.currentController}) - s.Require().Error(err, "missing new controller signature should be rejected") - }) - - s.Run("reject: only new controller signs", func() { - key := s.mintNFT("nft-a-nc-only") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.newController}) - s.Require().Error(err, "missing current controller signature should be rejected") - }) -} - -// --- Scenario B: Controller set, Secured Party for eNote set -------------------------- - -func (s *ControllerAuthAcceptanceTestSuite) TestControllerSet_WithSecuredParty() { - controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER - securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE - - baseRoles := func() []types.RolesEntry { - return []types.RolesEntry{ - {Role: controllerRole, Addresses: []string{s.currentController}}, - {Role: securedPartyRole, Addresses: []string{s.securedParty}}, - } - } - - s.Run("happy path: current controller, secured party, and new controller sign", func() { - key := s.mintNFT("nft-b-happy") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.currentController, s.securedParty, s.newController}) - s.Require().NoError(err, "all three required signers should authorize the update") - - s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController, - "new controller should be added to the CONTROLLER role") - }) - - s.Run("reject: only current controller signs", func() { - key := s.mintNFT("nft-b-cc-only") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.currentController}) - s.Require().Error(err) - }) - - s.Run("reject: only new controller signs", func() { - key := s.mintNFT("nft-b-nc-only") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.newController}) - s.Require().Error(err) - }) - - s.Run("reject: only secured party signs", func() { - key := s.mintNFT("nft-b-sp-only") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.securedParty}) - s.Require().Error(err) - }) - - s.Run("reject: current controller and secured party sign (missing new controller)", func() { - key := s.mintNFT("nft-b-cc-sp") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.currentController, s.securedParty}) - s.Require().Error(err) - }) - - s.Run("reject: current controller and new controller sign (missing secured party)", func() { - key := s.mintNFT("nft-b-cc-nc") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.currentController, s.newController}) - s.Require().Error(err, "secured party signature is required when it is set") - }) - - s.Run("reject: secured party and new controller sign (missing current controller)", func() { - key := s.mintNFT("nft-b-sp-nc") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.securedParty, s.newController}) - s.Require().Error(err, "current controller signature is always required (no unilateral takeover)") - }) - - s.Run("reject: an unrelated stranger signs", func() { - key := s.mintNFT("nft-b-stranger") - s.setupRegistry(key, baseRoles()) - - err := s.grantController(key, []string{s.stranger}) - s.Require().Error(err) - }) -} - -// --- AuthZ grant scenarios ------------------------------------------------------------ -// -// The ticket asks us to verify the same scenarios "but signed with authz grants". These tests -// document a hard architectural constraint discovered during the PoC: the Cosmos SDK authz -// module can only dispatch messages that have exactly one signer (see -// x/authz/keeper/keeper.go DispatchActions, which returns ErrAuthorizationNumOfSigners when -// len(signers) != 1). -// -// Because a Controller update always requires at least two signers (current controller and new -// controller, plus the secured party when set), a multi-signer MsgGrantRole cannot be executed -// through an authz MsgExec at all. Delegation via authz would therefore require a different -// multisig model (e.g. a single multisig account/key whose message has one signer), which is out -// of scope for this PoC. - -// mustAddr converts a bech32 string into an sdk.AccAddress, failing the test on error. -func (s *ControllerAuthAcceptanceTestSuite) mustAddr(addr string) sdk.AccAddress { - a, err := sdk.AccAddressFromBech32(addr) - s.Require().NoError(err) - return a -} - -// grantGrantRoleAuthz grants the executor authority to execute MsgGrantRole on behalf of granter. -func (s *ControllerAuthAcceptanceTestSuite) grantGrantRoleAuthz(executor, granter string) { - auth := authz.NewGenericAuthorization(sdk.MsgTypeURL(&types.MsgGrantRole{})) - s.Require().NoError(s.app.AuthzKeeper.SaveGrant(s.ctx, s.mustAddr(executor), s.mustAddr(granter), auth, nil)) -} - -// execGrantControllerViaAuthz submits a MsgGrantRole (with the given signers) wrapped in a -// MsgExec run by executor, and returns the resulting error. -func (s *ControllerAuthAcceptanceTestSuite) execGrantControllerViaAuthz(executor string, key *types.RegistryKey, signers []string) error { - inner := &types.MsgGrantRole{ - Signers: signers, - Key: key, - Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, - Addresses: []string{s.newController}, - } - msgExec := authz.NewMsgExec(s.mustAddr(executor), []sdk.Msg{inner}) - _, err := s.app.AuthzKeeper.Exec(s.ctx, &msgExec) - return err -} - -func (s *ControllerAuthAcceptanceTestSuite) TestControllerUpdate_ViaAuthzGrants() { - controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER - securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE - executor := genAddr() - - s.Run("authz cannot dispatch a multi-signer controller update", func() { - key := s.mintNFT("nft-authz-multisig") - s.setupRegistry(key, []types.RolesEntry{ - {Role: controllerRole, Addresses: []string{s.currentController}}, - {Role: securedPartyRole, Addresses: []string{s.securedParty}}, - }) - - // Even with grants from every required party, authz refuses to dispatch because the - // inner MsgGrantRole has more than one signer. - s.grantGrantRoleAuthz(executor, s.currentController) - s.grantGrantRoleAuthz(executor, s.securedParty) - s.grantGrantRoleAuthz(executor, s.newController) - - err := s.execGrantControllerViaAuthz(executor, key, []string{s.currentController, s.securedParty, s.newController}) - s.Require().Error(err, "authz must reject a multi-signer message") - s.Require().ErrorIs(err, authz.ErrAuthorizationNumOfSigners, - "authz rejects multi-signer messages with ErrAuthorizationNumOfSigners") - - // The controller role must remain unchanged since the update never executed. - s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) - }) -} diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 5ac0487af8..329efd0659 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -57,16 +57,14 @@ func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*typ roleAuths := types.RoleAuthorizationMap() if roleAuth, ok := roleAuths[msg.Role]; ok { - // Policy-based multi-signer path: the incoming addresses are the "new" assignment. - if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, msg.Addresses, msg.Signers); err != nil { + // Policy-based path: the incoming addresses are the "new" assignment. A single signer must + // satisfy the policy on its own; multi-party changes go through ProposeRoleChange. + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, msg.Addresses, []string{msg.Signer}); err != nil { return nil, err } } else { - // Legacy fallback: first signer must own the NFT. - if len(msg.Signers) == 0 { - return nil, types.NewErrCodeUnauthorized("no signers provided") - } - if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signers[0]); err != nil { + // Legacy fallback: the signer must own the NFT. + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { return nil, err } } @@ -94,16 +92,14 @@ func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*t roleAuths := types.RoleAuthorizationMap() if roleAuth, ok := roleAuths[msg.Role]; ok { - // Policy-based multi-signer path. For revoke, no new addresses are being assigned. - if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, nil, msg.Signers); err != nil { + // Policy-based path. For revoke, no new addresses are being assigned. A single signer must + // satisfy the policy on its own; multi-party changes go through ProposeRoleChange. + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, nil, []string{msg.Signer}); err != nil { return nil, err } } else { - // Legacy fallback: first signer must own the NFT. - if len(msg.Signers) == 0 { - return nil, types.NewErrCodeUnauthorized("no signers provided") - } - if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signers[0]); err != nil { + // Legacy fallback: the signer must own the NFT. + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { return nil, err } } @@ -133,16 +129,14 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types roleAuths := types.RoleAuthorizationMap() for _, update := range msg.RoleUpdates { if roleAuth, ok := roleAuths[update.Role]; ok { - // The new addresses for this role are the desired state from the update. - if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, update.Addresses, msg.Signers); err != nil { + // The new addresses for this role are the desired state from the update. A single signer + // must satisfy the policy on its own; multi-party changes go through ProposeRoleChange. + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, update.Addresses, []string{msg.Signer}); err != nil { return nil, err } } else { - // Legacy fallback: first signer must own the NFT. - if len(msg.Signers) == 0 { - return nil, types.NewErrCodeUnauthorized("no signers provided") - } - if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signers[0]); err != nil { + // Legacy fallback: the signer must own the NFT. + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { return nil, err } } diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index ec3e2e5af8..98999da563 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -11,6 +11,7 @@ import ( "cosmossdk.io/x/nft" nftkeeper "cosmossdk.io/x/nft/keeper" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/authz" @@ -53,6 +54,11 @@ func TestRoleChangeAccumulationAcceptanceTestSuite(t *testing.T) { suite.Run(t, new(RoleChangeAccumulationAcceptanceTestSuite)) } +// genAddr generates a fresh bech32 account address. +func genAddr() string { + return sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()).String() +} + func (s *RoleChangeAccumulationAcceptanceTestSuite) SetupTest() { s.app = app.Setup(s.T()) s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 6832a21c1e..ddd9242f06 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -44,14 +44,8 @@ func (m MsgRegisterNFT) ValidateBasic() error { // ValidateBasic validates the MsgGrantRole message func (m MsgGrantRole) ValidateBasic() error { var errs []error - if len(m.Signers) == 0 { - errs = append(errs, NewErrCodeInvalidField("signers", "at least one signer is required")) - } else { - for _, signer := range m.Signers { - if _, err := sdk.AccAddressFromBech32(signer); err != nil { - errs = append(errs, NewErrCodeInvalidField("signers", "%s", err)) - } - } + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) } if err := m.Key.Validate(); err != nil { @@ -72,14 +66,8 @@ func (m MsgGrantRole) ValidateBasic() error { // ValidateBasic validates the MsgRevokeRole message func (m MsgRevokeRole) ValidateBasic() error { var errs []error - if len(m.Signers) == 0 { - errs = append(errs, NewErrCodeInvalidField("signers", "at least one signer is required")) - } else { - for _, signer := range m.Signers { - if _, err := sdk.AccAddressFromBech32(signer); err != nil { - errs = append(errs, NewErrCodeInvalidField("signers", "%s", err)) - } - } + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) } if err := m.Key.Validate(); err != nil { @@ -100,14 +88,8 @@ func (m MsgRevokeRole) ValidateBasic() error { // ValidateBasic validates the MsgSetRoles message func (m MsgSetRoles) ValidateBasic() error { var errs []error - if len(m.Signers) == 0 { - errs = append(errs, NewErrCodeInvalidField("signers", "at least one signer is required")) - } else { - for _, signer := range m.Signers { - if _, err := sdk.AccAddressFromBech32(signer); err != nil { - errs = append(errs, NewErrCodeInvalidField("signers", "%s", err)) - } - } + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) } if err := m.Key.Validate(); err != nil { diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index f6e31f8f77..47b11fcc49 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -17,12 +17,11 @@ func TestAllMsgsGetSigners(t *testing.T) { func(signer string) sdk.Msg { return &MsgRegistryBulkUpdate{Signer: signer} }, func(signer string) sdk.Msg { return &MsgProposeRoleChange{Signer: signer} }, func(signer string) sdk.Msg { return &MsgApproveRoleChange{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgGrantRole{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgRevokeRole{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgSetRoles{Signer: signer} }, } - msgMakersMulti := []testutil.MsgMakerMulti{ - func(signers []string) sdk.Msg { return &MsgGrantRole{Signers: signers} }, - func(signers []string) sdk.Msg { return &MsgRevokeRole{Signers: signers} }, - func(signers []string) sdk.Msg { return &MsgSetRoles{Signers: signers} }, - } + msgMakersMulti := []testutil.MsgMakerMulti{} testutil.RunGetSignersTests(t, AllRequestMsgs, msgMakers, msgMakersMulti) } @@ -69,13 +68,13 @@ func TestMsgGrantRole_ValidateBasic(t *testing.T) { msg MsgGrantRole exp string }{ - {name: "valid", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}}, - {name: "no signers", msg: MsgGrantRole{Signers: []string{}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signers: at least one signer is required"}, - {name: "bad signer", msg: MsgGrantRole{Signers: []string{"bad"}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signers: decoding bech32"}, - {name: "nil key", msg: MsgGrantRole{Signers: []string{validAddr}, Key: nil, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, - {name: "invalid role", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, - {name: "no addresses", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, - {name: "bad address", msg: MsgGrantRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + {name: "valid", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}}, + {name: "empty signer", msg: MsgGrantRole{Signer: "", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signer: empty address"}, + {name: "bad signer", msg: MsgGrantRole{Signer: "bad", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid signer: decoding bech32"}, + {name: "nil key", msg: MsgGrantRole{Signer: validAddr, Key: nil, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, + {name: "invalid role", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, + {name: "no addresses", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, + {name: "bad address", msg: MsgGrantRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, } for _, tc := range tests { @@ -100,13 +99,13 @@ func TestMsgRevokeRole_ValidateBasic(t *testing.T) { msg MsgRevokeRole exp string }{ - {name: "valid", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}}, - {name: "no signers", msg: MsgRevokeRole{Signers: []string{}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signers: at least one signer is required"}, - {name: "bad signer", msg: MsgRevokeRole{Signers: []string{"bad"}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signers: decoding bech32"}, - {name: "nil key", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: nil, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, - {name: "invalid role", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, - {name: "no addresses", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, - {name: "bad address", msg: MsgRevokeRole{Signers: []string{validAddr}, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + {name: "valid", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}}, + {name: "empty signer", msg: MsgRevokeRole{Signer: "", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signer: empty address"}, + {name: "bad signer", msg: MsgRevokeRole{Signer: "bad", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid signer: decoding bech32"}, + {name: "nil key", msg: MsgRevokeRole{Signer: validAddr, Key: nil, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, + {name: "invalid role", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, + {name: "no addresses", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, + {name: "bad address", msg: MsgRevokeRole{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_ORIGINATOR, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, } for _, tc := range tests { @@ -132,14 +131,14 @@ func TestMsgSetRoles_ValidateBasic(t *testing.T) { msg MsgSetRoles exp string }{ - {name: "valid", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}}, - {name: "valid clear role", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER}}}}, - {name: "no signers", msg: MsgSetRoles{Signers: []string{}, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signers: at least one signer is required"}, - {name: "bad signer", msg: MsgSetRoles{Signers: []string{"bad"}, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signers: decoding bech32"}, - {name: "nil key", msg: MsgSetRoles{Signers: []string{validAddr}, Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: registry key cannot be nil"}, - {name: "no role_updates", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, - {name: "unspecified role", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, - {name: "bad address in update", msg: MsgSetRoles{Signers: []string{validAddr}, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, + {name: "valid", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}}, + {name: "valid clear role", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER}}}}, + {name: "empty signer", msg: MsgSetRoles{Signer: "", Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signer: empty address"}, + {name: "bad signer", msg: MsgSetRoles{Signer: "bad", Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signer: decoding bech32"}, + {name: "nil key", msg: MsgSetRoles{Signer: validAddr, Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: registry key cannot be nil"}, + {name: "no role_updates", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, + {name: "unspecified role", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, + {name: "bad address in update", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } for _, tc := range tests { diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 2b51d6c136..582b24c7b3 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -138,12 +138,11 @@ var xxx_messageInfo_MsgRegisterNFTResponse proto.InternalMessageInfo // MsgGrantRole represents a message to grant a role to one or more addresses. // This message adds the specified addresses to an existing role for the given registry key. -// Multiple signers enable multi-party authorization workflows. type MsgGrantRole struct { - // signers are the addresses authorizing the role grant. - // When a RegistryClass authorization policy is defined for the target role, all required - // role holders must be included here. Without a policy, a single NFT owner address is required. - Signers []string `protobuf:"bytes,1,rep,name=signers,proto3" json:"signers,omitempty"` + // signer is the address authorizing the role grant. The signer must satisfy the role's + // authorization policy on its own (e.g. the NFT owner for roles without a policy). Multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange instead. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // key is the registry key to grant the role to. // This identifies the specific registry entry to modify. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` @@ -188,11 +187,11 @@ func (m *MsgGrantRole) XXX_DiscardUnknown() { var xxx_messageInfo_MsgGrantRole proto.InternalMessageInfo -func (m *MsgGrantRole) GetSigners() []string { +func (m *MsgGrantRole) GetSigner() string { if m != nil { - return m.Signers + return m.Signer } - return nil + return "" } func (m *MsgGrantRole) GetKey() *RegistryKey { @@ -256,12 +255,11 @@ var xxx_messageInfo_MsgGrantRoleResponse proto.InternalMessageInfo // MsgRevokeRole represents a message to revoke a role from one or more addresses. // This message removes the specified addresses from an existing role for the given registry key. -// Multiple signers enable multi-party authorization workflows. type MsgRevokeRole struct { - // signers are the addresses authorizing the role revocation. - // When a RegistryClass authorization policy is defined for the target role, all required - // role holders must be included here. Without a policy, a single NFT owner address is required. - Signers []string `protobuf:"bytes,1,rep,name=signers,proto3" json:"signers,omitempty"` + // signer is the address authorizing the role revocation. The signer must satisfy the role's + // authorization policy on its own (e.g. the NFT owner for roles without a policy). Multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange instead. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // key is the registry key to revoke the role from. // This identifies the specific registry entry to modify. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` @@ -306,11 +304,11 @@ func (m *MsgRevokeRole) XXX_DiscardUnknown() { var xxx_messageInfo_MsgRevokeRole proto.InternalMessageInfo -func (m *MsgRevokeRole) GetSigners() []string { +func (m *MsgRevokeRole) GetSigner() string { if m != nil { - return m.Signers + return m.Signer } - return nil + return "" } func (m *MsgRevokeRole) GetKey() *RegistryKey { @@ -569,9 +567,10 @@ var xxx_messageInfo_MsgRegistryBulkUpdateResponse proto.InternalMessageInfo // addresses and applies grants and revokes to reach the desired state. // All updates succeed or all fail (atomic). type MsgSetRoles struct { - // signers are the addresses authorizing the role updates. - // All required role holders per the RegistryClass authorization policy must be present. - Signers []string `protobuf:"bytes,1,rep,name=signers,proto3" json:"signers,omitempty"` + // signer is the address authorizing the role updates. The signer must satisfy each affected + // role's authorization policy on its own. Multi-party role changes are made through + // ProposeRoleChange / ApproveRoleChange instead. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // key identifies the registry entry to update. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` // role_updates contains the desired state for each role to update. @@ -612,11 +611,11 @@ func (m *MsgSetRoles) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSetRoles proto.InternalMessageInfo -func (m *MsgSetRoles) GetSigners() []string { +func (m *MsgSetRoles) GetSigner() string { if m != nil { - return m.Signers + return m.Signer } - return nil + return "" } func (m *MsgSetRoles) GetKey() *RegistryKey { @@ -991,61 +990,59 @@ func init() { func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 850 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0xcf, 0x6e, 0xeb, 0x44, - 0x14, 0xc6, 0x33, 0x49, 0xfa, 0x27, 0x27, 0xbd, 0x57, 0x60, 0x7a, 0x7b, 0x7d, 0x0d, 0xa4, 0x51, - 0x6e, 0x8b, 0xa2, 0x96, 0x24, 0x6d, 0x68, 0x51, 0xd5, 0x05, 0x52, 0x83, 0x0a, 0xaa, 0xaa, 0x40, - 0xe5, 0xd2, 0x0d, 0x42, 0x8a, 0xdc, 0x64, 0xe4, 0x5a, 0x69, 0x3c, 0xd6, 0x8c, 0x13, 0x9a, 0x2e, - 0x10, 0x62, 0xc5, 0x92, 0x17, 0xe0, 0x1d, 0xba, 0xe0, 0x19, 0x50, 0xd9, 0x55, 0xb0, 0x61, 0x85, - 0x50, 0xbb, 0xe8, 0x9a, 0x25, 0xac, 0x90, 0xc7, 0xf6, 0xc4, 0x6e, 0x12, 0xc7, 0x6d, 0xa5, 0xb2, - 0x60, 0x97, 0xb1, 0x7f, 0x67, 0xce, 0x77, 0xbe, 0x39, 0x33, 0x13, 0xc3, 0xa2, 0x45, 0x49, 0x0f, - 0x9b, 0x9a, 0xd9, 0xc4, 0x15, 0x8a, 0x75, 0x83, 0xd9, 0xb4, 0x5f, 0xe9, 0xad, 0x57, 0xec, 0xb3, - 0xb2, 0x45, 0x89, 0x4d, 0xa4, 0x85, 0x01, 0x50, 0xf6, 0x81, 0x72, 0x6f, 0x5d, 0x99, 0xd7, 0x89, - 0x4e, 0x38, 0x52, 0x71, 0x7e, 0xb9, 0xb4, 0xf2, 0xaa, 0x49, 0x58, 0x87, 0xb0, 0x86, 0xfb, 0xc2, - 0x1d, 0x78, 0xaf, 0x5e, 0xba, 0xa3, 0x4a, 0x87, 0xe9, 0x4e, 0x82, 0x0e, 0xd3, 0xbd, 0x17, 0xcb, - 0x63, 0x24, 0x88, 0x6c, 0x2e, 0xb6, 0x32, 0x06, 0xd3, 0xba, 0xf6, 0x09, 0xa1, 0xc6, 0xb9, 0x66, - 0x1b, 0xc4, 0x74, 0xd9, 0xc2, 0xcf, 0x08, 0x9e, 0xd7, 0x99, 0xae, 0x72, 0x0c, 0xd3, 0xcf, 0x3e, - 0xf9, 0x42, 0x5a, 0x83, 0x69, 0x66, 0xe8, 0x26, 0xa6, 0x32, 0xca, 0xa3, 0x62, 0xa6, 0x26, 0xff, - 0xfa, 0x53, 0x69, 0xde, 0x13, 0xb8, 0xd3, 0x6a, 0x51, 0xcc, 0xd8, 0xa1, 0x4d, 0x0d, 0x53, 0x57, - 0x3d, 0x4e, 0xda, 0x84, 0x54, 0x1b, 0xf7, 0xe5, 0x64, 0x1e, 0x15, 0xb3, 0xd5, 0xd7, 0xe5, 0xd1, - 0x3e, 0x94, 0x55, 0xef, 0xf7, 0x3e, 0xee, 0xab, 0x0e, 0x2f, 0x7d, 0x04, 0x53, 0x94, 0x9c, 0x62, - 0x26, 0xa7, 0xf2, 0xa9, 0x62, 0xb6, 0x5a, 0x18, 0x1b, 0xe8, 0x40, 0xbb, 0xa6, 0x4d, 0xfb, 0xb5, - 0xf4, 0xe5, 0x1f, 0x8b, 0x09, 0xd5, 0x0d, 0xdb, 0xce, 0x7e, 0x77, 0x7b, 0xb1, 0xe2, 0x69, 0x28, - 0xc8, 0xb0, 0x10, 0xae, 0x43, 0xc5, 0xcc, 0x22, 0x26, 0xc3, 0x85, 0xbf, 0x11, 0xcc, 0xd5, 0x99, - 0xfe, 0x29, 0xd5, 0x4c, 0xdb, 0x99, 0x4a, 0xaa, 0xc2, 0x8c, 0x1b, 0xc4, 0x64, 0x94, 0x4f, 0x45, - 0x56, 0xe8, 0x83, 0x0f, 0x2d, 0x71, 0x0b, 0xd2, 0x8e, 0x56, 0x39, 0x95, 0x47, 0xc5, 0xe7, 0xd5, - 0xa5, 0x49, 0x71, 0x8e, 0x3c, 0x95, 0x47, 0x48, 0x1f, 0x42, 0x46, 0x73, 0xa5, 0x60, 0x26, 0xa7, - 0x27, 0xc8, 0x1c, 0xa0, 0xdb, 0x73, 0x8e, 0x29, 0xbe, 0xec, 0xc2, 0x02, 0xcc, 0x07, 0x4b, 0x17, - 0x9e, 0xfc, 0x83, 0xe0, 0x19, 0xb7, 0xab, 0x47, 0xda, 0xf8, 0xff, 0x66, 0xca, 0x4b, 0x78, 0x11, - 0xaa, 0x5d, 0xb8, 0xf2, 0x3d, 0x82, 0x37, 0xea, 0x4c, 0x3f, 0x32, 0xe9, 0x7f, 0xb0, 0x1d, 0xc2, - 0xed, 0xac, 0x80, 0x7c, 0x57, 0x89, 0x90, 0xf9, 0x23, 0xf2, 0x0a, 0x70, 0x27, 0xa8, 0x75, 0x4f, - 0xdb, 0x47, 0x56, 0x4b, 0xb3, 0xf1, 0x03, 0xb4, 0xee, 0xc2, 0x0c, 0x36, 0x6d, 0x6a, 0x60, 0x26, - 0x27, 0xf9, 0x2e, 0x5c, 0x9e, 0xa4, 0x37, 0xb8, 0x11, 0xfd, 0xd8, 0xb0, 0xf6, 0x45, 0x78, 0x77, - 0xa4, 0x3c, 0x51, 0xc0, 0x6f, 0x08, 0xb2, 0x75, 0xa6, 0x1f, 0x62, 0xde, 0x94, 0xec, 0x29, 0x7b, - 0x6f, 0x1f, 0xe6, 0x9c, 0x4e, 0x6a, 0x74, 0xb9, 0xa2, 0x58, 0x47, 0x8f, 0x2b, 0xde, 0xab, 0x38, - 0x4b, 0xc5, 0x93, 0xbb, 0x6d, 0xf5, 0x0d, 0xc0, 0x00, 0x17, 0x4d, 0x8e, 0x1e, 0xd7, 0xe4, 0xc9, - 0xd8, 0x4d, 0x5e, 0x78, 0x01, 0x6f, 0x05, 0x4c, 0x15, 0x66, 0xff, 0x92, 0xe4, 0x67, 0xc0, 0x01, - 0x25, 0x16, 0x61, 0xbc, 0xdf, 0x3f, 0x3e, 0xd1, 0x4c, 0x1d, 0x3f, 0xdd, 0x39, 0xff, 0xf0, 0xfd, - 0xbe, 0x07, 0x19, 0x62, 0x61, 0xca, 0x2f, 0x2c, 0x39, 0xcd, 0xc3, 0x57, 0xa3, 0x96, 0xca, 0xad, - 0xec, 0x73, 0x3f, 0x44, 0x1d, 0x44, 0x87, 0x5d, 0x9d, 0x8a, 0x7f, 0x74, 0x84, 0x3a, 0xfb, 0x08, - 0xde, 0x19, 0x65, 0xa5, 0xef, 0xb5, 0xf4, 0x36, 0x64, 0x9a, 0xfc, 0x49, 0xc3, 0x68, 0xb9, 0xae, - 0xaa, 0xb3, 0xee, 0x83, 0xbd, 0x96, 0x24, 0xc3, 0x8c, 0x66, 0x59, 0xa7, 0x06, 0x6e, 0x71, 0x07, - 0x67, 0x55, 0x7f, 0x58, 0xa0, 0x7c, 0x85, 0x76, 0x2c, 0x5e, 0xd9, 0xa3, 0x56, 0x28, 0x24, 0x20, - 0x19, 0x16, 0x10, 0x2e, 0x65, 0x8b, 0x97, 0x32, 0x94, 0x53, 0x94, 0x12, 0x50, 0x8b, 0x42, 0x6a, - 0xab, 0x7f, 0x4d, 0x43, 0xaa, 0xce, 0x74, 0x09, 0x43, 0x36, 0xf8, 0xb7, 0xe1, 0xbd, 0x71, 0x0b, - 0x13, 0xbe, 0x96, 0x95, 0x72, 0x3c, 0x4e, 0x08, 0x69, 0x40, 0x66, 0x70, 0x75, 0x2f, 0x45, 0x04, - 0x0b, 0x4a, 0x79, 0x3f, 0x0e, 0x25, 0x12, 0x1c, 0x03, 0x04, 0xee, 0xc1, 0xe5, 0x48, 0x79, 0x3e, - 0xa6, 0x94, 0x62, 0x61, 0x22, 0x47, 0x1b, 0x9e, 0x85, 0x6f, 0x95, 0x62, 0x44, 0x7c, 0x88, 0x54, - 0xd6, 0xe2, 0x92, 0x22, 0xd9, 0x39, 0x48, 0x23, 0xee, 0x86, 0xd2, 0x44, 0xdf, 0x83, 0xb8, 0xb2, - 0x79, 0x2f, 0x5c, 0xe4, 0xfe, 0x0a, 0x66, 0xc5, 0xb1, 0xfe, 0x3a, 0x62, 0x0a, 0x1f, 0x52, 0x56, - 0x63, 0x40, 0x62, 0xf6, 0xaf, 0xe1, 0xcd, 0xe1, 0x73, 0x2c, 0x6a, 0xb5, 0x87, 0x68, 0x65, 0xe3, - 0x3e, 0x74, 0x30, 0xf1, 0xf0, 0xf6, 0x8c, 0x4a, 0x3c, 0x44, 0x47, 0x26, 0x1e, 0xbb, 0x0d, 0x95, - 0xa9, 0x6f, 0x6f, 0x2f, 0x56, 0x50, 0xad, 0x7d, 0x79, 0x9d, 0x43, 0x57, 0xd7, 0x39, 0xf4, 0xe7, - 0x75, 0x0e, 0xfd, 0x70, 0x93, 0x4b, 0x5c, 0xdd, 0xe4, 0x12, 0xbf, 0xdf, 0xe4, 0x12, 0xf0, 0xca, - 0x20, 0x63, 0x26, 0x3e, 0x40, 0x5f, 0x6e, 0xe8, 0x86, 0x7d, 0xd2, 0x3d, 0x2e, 0x37, 0x49, 0xa7, - 0x32, 0x80, 0x4a, 0x06, 0x09, 0x8c, 0x2a, 0x67, 0x83, 0x8f, 0x04, 0xbb, 0x6f, 0x61, 0x76, 0x3c, - 0xcd, 0x3f, 0x0d, 0x3e, 0xf8, 0x37, 0x00, 0x00, 0xff, 0xff, 0x36, 0x32, 0x5f, 0x21, 0xf2, 0x0c, - 0x00, 0x00, + // 829 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0xcf, 0x4f, 0xe3, 0x46, + 0x14, 0xc7, 0x33, 0x49, 0xf8, 0x91, 0x17, 0x40, 0xad, 0xcb, 0x0f, 0xe3, 0xb6, 0x21, 0x0a, 0x50, + 0x45, 0xd0, 0x24, 0x90, 0x42, 0x85, 0x7a, 0xa8, 0x44, 0x2a, 0x5a, 0x21, 0x94, 0x16, 0x99, 0x72, + 0xa9, 0x2a, 0x45, 0x26, 0x19, 0x19, 0x2b, 0xc4, 0x63, 0xcd, 0x38, 0x29, 0xe1, 0x50, 0x55, 0x3d, + 0xf5, 0xd8, 0x7f, 0xa0, 0xff, 0x03, 0x87, 0xfe, 0x0d, 0x15, 0xbd, 0xa1, 0x3d, 0xed, 0x69, 0xb5, + 0x82, 0x03, 0xe7, 0xbd, 0xec, 0x5e, 0x57, 0x1e, 0x3b, 0x13, 0x9b, 0x24, 0x26, 0x80, 0xc4, 0x6a, + 0xb5, 0xb7, 0x8c, 0xfd, 0x79, 0xf3, 0xbe, 0xef, 0x3b, 0xf3, 0x66, 0x62, 0x58, 0xb0, 0x28, 0x69, + 0x61, 0x53, 0x33, 0xab, 0xb8, 0x40, 0xb1, 0x6e, 0x30, 0x9b, 0xb6, 0x0b, 0xad, 0xf5, 0x82, 0x7d, + 0x9a, 0xb7, 0x28, 0xb1, 0x89, 0x34, 0xdb, 0x05, 0xf2, 0x1d, 0x20, 0xdf, 0x5a, 0x57, 0xa6, 0x75, + 0xa2, 0x13, 0x8e, 0x14, 0x9c, 0x5f, 0x2e, 0xad, 0xcc, 0x57, 0x09, 0x6b, 0x10, 0x56, 0x71, 0x5f, + 0xb8, 0x03, 0xef, 0xd5, 0x9c, 0x3b, 0x2a, 0x34, 0x98, 0xee, 0x24, 0x68, 0x30, 0xdd, 0x7b, 0xb1, + 0x3c, 0x40, 0x82, 0xc8, 0xe6, 0x62, 0x2b, 0x03, 0x30, 0xad, 0x69, 0x1f, 0x13, 0x6a, 0x9c, 0x69, + 0xb6, 0x41, 0x4c, 0x97, 0xcd, 0xfc, 0x87, 0x60, 0xaa, 0xcc, 0x74, 0x95, 0x63, 0x98, 0xfe, 0xf8, + 0xfd, 0xcf, 0xd2, 0x1a, 0x8c, 0x32, 0x43, 0x37, 0x31, 0x95, 0x51, 0x1a, 0x65, 0x13, 0x25, 0xf9, + 0xd9, 0xbf, 0xb9, 0x69, 0x4f, 0xe0, 0x76, 0xad, 0x46, 0x31, 0x63, 0x07, 0x36, 0x35, 0x4c, 0x5d, + 0xf5, 0x38, 0x69, 0x13, 0x62, 0x75, 0xdc, 0x96, 0xa3, 0x69, 0x94, 0x4d, 0x16, 0x17, 0xf3, 0xfd, + 0x7d, 0xc8, 0xab, 0xde, 0xef, 0x3d, 0xdc, 0x56, 0x1d, 0x5e, 0xfa, 0x16, 0x46, 0x28, 0x39, 0xc1, + 0x4c, 0x8e, 0xa5, 0x63, 0xd9, 0x64, 0x31, 0x33, 0x30, 0xd0, 0x81, 0x76, 0x4c, 0x9b, 0xb6, 0x4b, + 0xf1, 0x8b, 0x17, 0x0b, 0x11, 0xd5, 0x0d, 0xfb, 0x26, 0xf9, 0xe7, 0xcd, 0xf9, 0x8a, 0xa7, 0x21, + 0x23, 0xc3, 0x6c, 0xb0, 0x0e, 0x15, 0x33, 0x8b, 0x98, 0x0c, 0x67, 0x5e, 0x23, 0x98, 0x28, 0x33, + 0xfd, 0x07, 0xaa, 0x99, 0xb6, 0x33, 0xd5, 0xd3, 0x15, 0xb8, 0x05, 0x71, 0x47, 0xa9, 0x1c, 0x4b, + 0xa3, 0xec, 0x54, 0x71, 0xe9, 0xae, 0x38, 0x47, 0x9c, 0xca, 0x23, 0xa4, 0xaf, 0x21, 0xa1, 0xb9, + 0x4a, 0x30, 0x93, 0xe3, 0xe9, 0x58, 0xa8, 0xca, 0x2e, 0x1a, 0xb4, 0x64, 0x16, 0xa6, 0xfd, 0x75, + 0x0b, 0x43, 0xde, 0x20, 0x98, 0xe4, 0x5e, 0xb5, 0x48, 0x1d, 0x7f, 0x50, 0x8e, 0xcc, 0xc1, 0x4c, + 0xa0, 0x70, 0x61, 0xc9, 0x5f, 0x08, 0x3e, 0x2a, 0x33, 0xfd, 0xd0, 0xa4, 0xef, 0xa0, 0x11, 0x82, + 0x1a, 0x15, 0x90, 0x6f, 0x2b, 0x11, 0x32, 0xff, 0x41, 0x5e, 0x01, 0xee, 0x04, 0xa5, 0xe6, 0x49, + 0xfd, 0xd0, 0xaa, 0x69, 0xf6, 0x43, 0x56, 0x70, 0x07, 0xc6, 0xb0, 0x69, 0x53, 0x03, 0x33, 0x39, + 0xca, 0xfb, 0x6f, 0xf9, 0x2e, 0xbd, 0xfe, 0x16, 0xec, 0xc4, 0x06, 0xb5, 0x2f, 0xc0, 0xe7, 0x7d, + 0xe5, 0x89, 0x02, 0x2e, 0x11, 0x24, 0xcb, 0x4c, 0x3f, 0xc0, 0x7c, 0x47, 0xb2, 0xa7, 0xdb, 0x78, + 0x7b, 0x30, 0xe1, 0x6c, 0xa3, 0x4a, 0x93, 0xeb, 0x19, 0xea, 0xc8, 0x71, 0xa5, 0x7b, 0xf5, 0x26, + 0xa9, 0x78, 0x72, 0xab, 0xe6, 0xdf, 0x01, 0xba, 0xb4, 0xd8, 0xe0, 0xe8, 0x71, 0x1b, 0x3c, 0x3a, + 0xf4, 0x06, 0xcf, 0xcc, 0xc0, 0x27, 0x3e, 0x47, 0x85, 0xd3, 0xff, 0x47, 0x79, 0xf7, 0xef, 0x53, + 0x62, 0x11, 0xc6, 0x37, 0xfb, 0x77, 0xc7, 0x9a, 0xa9, 0xbf, 0x17, 0xbd, 0xbe, 0x0b, 0x09, 0x62, + 0x61, 0xca, 0xef, 0x29, 0x39, 0xce, 0xc3, 0x57, 0xc3, 0x56, 0xca, 0xad, 0xec, 0xa7, 0x4e, 0x88, + 0xda, 0x8d, 0x0e, 0xba, 0x3a, 0xf2, 0xc0, 0x63, 0xe3, 0x10, 0x3e, 0xeb, 0x67, 0x65, 0xc7, 0x6b, + 0xe9, 0x53, 0x48, 0x54, 0xf9, 0x93, 0x8a, 0x51, 0x73, 0x5d, 0x55, 0xc7, 0xdd, 0x07, 0xbb, 0x35, + 0x49, 0x86, 0x31, 0xcd, 0xb2, 0x4e, 0x0c, 0x5c, 0xe3, 0x0e, 0x8e, 0xab, 0x9d, 0x61, 0x86, 0xf2, + 0x15, 0xda, 0xb6, 0x78, 0x65, 0x8f, 0x5a, 0xa1, 0x80, 0x80, 0x68, 0x50, 0x40, 0xb0, 0x94, 0x2d, + 0x5e, 0x4a, 0x4f, 0x4e, 0x51, 0x8a, 0x4f, 0x2d, 0x0a, 0xa8, 0x2d, 0xbe, 0x1a, 0x85, 0x58, 0x99, + 0xe9, 0x12, 0x86, 0xa4, 0xff, 0xdf, 0xc2, 0x17, 0x83, 0x16, 0x26, 0x78, 0x1b, 0x2b, 0xf9, 0xe1, + 0x38, 0x21, 0xa4, 0x02, 0x89, 0xee, 0x8d, 0xbd, 0x14, 0x12, 0x2c, 0x28, 0xe5, 0xcb, 0x61, 0x28, + 0x91, 0xe0, 0x08, 0xc0, 0x77, 0x03, 0x2e, 0x87, 0xca, 0xeb, 0x60, 0x4a, 0x6e, 0x28, 0x4c, 0xe4, + 0xa8, 0xc3, 0x64, 0xf0, 0x4a, 0xc9, 0x86, 0xc4, 0x07, 0x48, 0x65, 0x6d, 0x58, 0x52, 0x24, 0x3b, + 0x03, 0xa9, 0xcf, 0xc5, 0x90, 0xbb, 0xd3, 0x77, 0x3f, 0xae, 0x6c, 0xde, 0x0b, 0x17, 0xb9, 0x7f, + 0x85, 0x71, 0x71, 0xa6, 0x2f, 0x86, 0x4c, 0xd1, 0x81, 0x94, 0xd5, 0x21, 0x20, 0x31, 0xfb, 0x6f, + 0xf0, 0x71, 0xef, 0x39, 0x16, 0xb6, 0xda, 0x3d, 0xb4, 0xb2, 0x71, 0x1f, 0xda, 0x9f, 0xb8, 0xb7, + 0x3d, 0xc3, 0x12, 0xf7, 0xd0, 0xa1, 0x89, 0x07, 0xb6, 0xa1, 0x32, 0xf2, 0xc7, 0xcd, 0xf9, 0x0a, + 0x2a, 0xd5, 0x2f, 0xae, 0x52, 0xe8, 0xf2, 0x2a, 0x85, 0x5e, 0x5e, 0xa5, 0xd0, 0xdf, 0xd7, 0xa9, + 0xc8, 0xe5, 0x75, 0x2a, 0xf2, 0xfc, 0x3a, 0x15, 0x81, 0x79, 0x83, 0x0c, 0x98, 0x78, 0x1f, 0xfd, + 0xb2, 0xa1, 0x1b, 0xf6, 0x71, 0xf3, 0x28, 0x5f, 0x25, 0x8d, 0x42, 0x17, 0xca, 0x19, 0xc4, 0x37, + 0x2a, 0x9c, 0x76, 0xbf, 0x0d, 0xec, 0xb6, 0x85, 0xd9, 0xd1, 0x28, 0xff, 0x22, 0xf8, 0xea, 0x6d, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x08, 0x21, 0xe3, 0xd1, 0xe9, 0x0c, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1065,11 +1062,13 @@ type MsgClient interface { RegisterNFT(ctx context.Context, in *MsgRegisterNFT, opts ...grpc.CallOption) (*MsgRegisterNFTResponse, error) // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy the role's authorization policy directly; multi-party role + // changes are made through ProposeRoleChange / ApproveRoleChange. GrantRole(ctx context.Context, in *MsgGrantRole, opts ...grpc.CallOption) (*MsgGrantRoleResponse, error) // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy the role's authorization policy directly; multi-party role + // changes are made through ProposeRoleChange / ApproveRoleChange. RevokeRole(ctx context.Context, in *MsgRevokeRole, opts ...grpc.CallOption) (*MsgRevokeRoleResponse, error) // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry for the specified key. @@ -1079,7 +1078,8 @@ type MsgClient interface { // Each registry in this will cost one MsgRegisterNFT. RegistryBulkUpdate(ctx context.Context, in *MsgRegistryBulkUpdate, opts ...grpc.CallOption) (*MsgRegistryBulkUpdateResponse, error) // SetRoles atomically sets the desired state for one or more roles on a registry entry. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy each affected role's authorization policy directly; multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange. SetRoles(ctx context.Context, in *MsgSetRoles, opts ...grpc.CallOption) (*MsgSetRolesResponse, error) // ProposeRoleChange opens a pending role change that accumulates single-signer approvals. // The change auto-applies once the role's authorization policy is satisfied. @@ -1176,11 +1176,13 @@ type MsgServer interface { RegisterNFT(context.Context, *MsgRegisterNFT) (*MsgRegisterNFTResponse, error) // GrantRole grants a role to one or more addresses. // This adds the specified addresses to the role for the given registry key. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy the role's authorization policy directly; multi-party role + // changes are made through ProposeRoleChange / ApproveRoleChange. GrantRole(context.Context, *MsgGrantRole) (*MsgGrantRoleResponse, error) // RevokeRole revokes a role from one or more addresses. // This removes the specified addresses from the role for the given registry key. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy the role's authorization policy directly; multi-party role + // changes are made through ProposeRoleChange / ApproveRoleChange. RevokeRole(context.Context, *MsgRevokeRole) (*MsgRevokeRoleResponse, error) // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry for the specified key. @@ -1190,7 +1192,8 @@ type MsgServer interface { // Each registry in this will cost one MsgRegisterNFT. RegistryBulkUpdate(context.Context, *MsgRegistryBulkUpdate) (*MsgRegistryBulkUpdateResponse, error) // SetRoles atomically sets the desired state for one or more roles on a registry entry. - // Multiple signers are supported for multi-party authorization workflows. + // The single signer must satisfy each affected role's authorization policy directly; multi-party + // role changes are made through ProposeRoleChange / ApproveRoleChange. SetRoles(context.Context, *MsgSetRoles) (*MsgSetRolesResponse, error) // ProposeRoleChange opens a pending role change that accumulates single-signer approvals. // The change auto-applies once the role's authorization policy is satisfied. @@ -1544,14 +1547,12 @@ func (m *MsgGrantRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Signers) > 0 { - for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Signers[iNdEx]) - copy(dAtA[i:], m.Signers[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signers[iNdEx]))) - i-- - dAtA[i] = 0xa - } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -1625,14 +1626,12 @@ func (m *MsgRevokeRole) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Signers) > 0 { - for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Signers[iNdEx]) - copy(dAtA[i:], m.Signers[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signers[iNdEx]))) - i-- - dAtA[i] = 0xa - } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -1838,14 +1837,12 @@ func (m *MsgSetRoles) MarshalToSizedBuffer(dAtA []byte) (int, error) { i-- dAtA[i] = 0x12 } - if len(m.Signers) > 0 { - for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Signers[iNdEx]) - copy(dAtA[i:], m.Signers[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.Signers[iNdEx]))) - i-- - dAtA[i] = 0xa - } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa } return len(dAtA) - i, nil } @@ -2130,11 +2127,9 @@ func (m *MsgGrantRole) Size() (n int) { } var l int _ = l - if len(m.Signers) > 0 { - for _, s := range m.Signers { - l = len(s) - n += 1 + l + sovTx(uint64(l)) - } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) } if m.Key != nil { l = m.Key.Size() @@ -2167,11 +2162,9 @@ func (m *MsgRevokeRole) Size() (n int) { } var l int _ = l - if len(m.Signers) > 0 { - for _, s := range m.Signers { - l = len(s) - n += 1 + l + sovTx(uint64(l)) - } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) } if m.Key != nil { l = m.Key.Size() @@ -2258,11 +2251,9 @@ func (m *MsgSetRoles) Size() (n int) { } var l int _ = l - if len(m.Signers) > 0 { - for _, s := range m.Signers { - l = len(s) - n += 1 + l + sovTx(uint64(l)) - } + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) } if m.Key != nil { l = m.Key.Size() @@ -2617,7 +2608,7 @@ func (m *MsgGrantRole) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2645,7 +2636,7 @@ func (m *MsgGrantRole) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signers = append(m.Signers, string(dAtA[iNdEx:postIndex])) + m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -2836,7 +2827,7 @@ func (m *MsgRevokeRole) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -2864,7 +2855,7 @@ func (m *MsgRevokeRole) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signers = append(m.Signers, string(dAtA[iNdEx:postIndex])) + m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -3389,7 +3380,7 @@ func (m *MsgSetRoles) Unmarshal(dAtA []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3417,7 +3408,7 @@ func (m *MsgSetRoles) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Signers = append(m.Signers, string(dAtA[iNdEx:postIndex])) + m.Signer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { From 3725eff04c8ef5eac9ac278ab9ab108cc7c34aa1 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 18 Jun 2026 15:21:23 -0600 Subject: [PATCH 05/36] Add pending role-change queries, CLI, and genesis persistence Expose and persist the Option B pending role-change state: - query.proto: PendingRoleChange(id) and PendingRoleChanges(key, pagination) RPCs plus REST routes; keeper GetPendingRoleChanges paginated walk with optional key filter; query-server handlers and request Validate methods - genesis.proto: GenesisState.pending_role_changes; InitGenesis/ExportGenesis round-trip pending changes so in-flight approvals survive export/import - CLI query: pending-role-change, pending-role-changes - CLI tx: propose-role-change, approve-role-change - Tests: query-by-id, list (all/filtered/empty/paginated), genesis round-trip Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/genesis.proto | 4 + proto/provenance/registry/v1/query.proto | 43 + x/registry/client/cli/query.go | 74 ++ x/registry/client/cli/tx.go | 82 ++ x/registry/keeper/genesis.go | 13 + x/registry/keeper/pending.go | 29 + x/registry/keeper/pending_query_test.go | 146 +++ x/registry/keeper/query_server.go | 41 + x/registry/types/genesis.pb.go | 87 +- x/registry/types/msgs.go | 17 + x/registry/types/query.pb.go | 1117 ++++++++++++++++++-- x/registry/types/query.pb.gw.go | 184 ++++ 12 files changed, 1731 insertions(+), 106 deletions(-) create mode 100644 x/registry/keeper/pending_query_test.go diff --git a/proto/provenance/registry/v1/genesis.proto b/proto/provenance/registry/v1/genesis.proto index 7aa4c76c28..0ecc3fa5ed 100644 --- a/proto/provenance/registry/v1/genesis.proto +++ b/proto/provenance/registry/v1/genesis.proto @@ -14,4 +14,8 @@ message GenesisState { // entries is the list of registry entries. // These entries define the initial state of the registry module. repeated RegistryEntry entries = 1 [(gogoproto.nullable) = false]; + + // pending_role_changes is the list of role changes awaiting approval. + // These preserve in-flight multi-party role changes across genesis export/import. + repeated PendingRoleChange pending_role_changes = 2 [(gogoproto.nullable) = false]; } \ No newline at end of file diff --git a/proto/provenance/registry/v1/query.proto b/proto/provenance/registry/v1/query.proto index 77b4c6add9..be5c78bce1 100644 --- a/proto/provenance/registry/v1/query.proto +++ b/proto/provenance/registry/v1/query.proto @@ -30,6 +30,16 @@ service Query { option (google.api.http).get = "/provenance/registry/v1/has_role/{key.asset_class_id}/{key.nft_id}/{address}/{role}"; } + + // PendingRoleChange returns a single pending role change by its id. + rpc PendingRoleChange(QueryPendingRoleChangeRequest) returns (QueryPendingRoleChangeResponse) { + option (google.api.http).get = "/provenance/registry/v1/pending_role_change/{id}"; + } + + // PendingRoleChanges returns the pending role changes, optionally filtered by registry key. + rpc PendingRoleChanges(QueryPendingRoleChangesRequest) returns (QueryPendingRoleChangesResponse) { + option (google.api.http).get = "/provenance/registry/v1/pending_role_changes"; + } } // QueryGetRegistryRequest is the request type for the Query/GetRegistry RPC method. @@ -92,3 +102,36 @@ message QueryHasRoleResponse { // This boolean value indicates whether the role verification was successful. bool has_role = 1; } + +// QueryPendingRoleChangeRequest is the request type for the Query/PendingRoleChange RPC method. +message QueryPendingRoleChangeRequest { + // id is the deterministic identifier of the pending role change to retrieve. + string id = 1; +} + +// QueryPendingRoleChangeResponse is the response type for the Query/PendingRoleChange RPC method. +message QueryPendingRoleChangeResponse { + // pending_role_change is the pending role change for the requested id. + PendingRoleChange pending_role_change = 1 [(gogoproto.nullable) = false]; +} + +// QueryPendingRoleChangesRequest is the paginated request type for the Query/PendingRoleChanges +// RPC method. +message QueryPendingRoleChangesRequest { + // key optionally filters the results to a single registry entry. When unset, all pending role + // changes are returned. + RegistryKey key = 1; + + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 99; +} + +// QueryPendingRoleChangesResponse is the paginated response type for the Query/PendingRoleChanges +// RPC method. +message QueryPendingRoleChangesResponse { + // pending_role_changes is the collection of pending role changes. + repeated PendingRoleChange pending_role_changes = 1 [(gogoproto.nullable) = false]; + + // pagination is the pagination details for this response. + cosmos.base.query.v1beta1.PageResponse pagination = 99; +} diff --git a/x/registry/client/cli/query.go b/x/registry/client/cli/query.go index 08d05d0a49..00271c336e 100644 --- a/x/registry/client/cli/query.go +++ b/x/registry/client/cli/query.go @@ -25,6 +25,8 @@ func CmdQuery() *cobra.Command { GetCmdQueryRegistry(), GetCmdQueryRegistries(), GetCmdQueryHasRole(), + GetCmdQueryPendingRoleChange(), + GetCmdQueryPendingRoleChanges(), ) return cmd @@ -141,3 +143,75 @@ func GetCmdQueryHasRole() *cobra.Command { flags.AddQueryFlagsToCmd(cmd) return cmd } + +// GetCmdQueryPendingRoleChange returns the command for querying a single pending role change by id +func GetCmdQueryPendingRoleChange() *cobra.Command { + cmd := &cobra.Command{ + Use: "pending-role-change ", + Short: "Query a pending role change by its id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.PendingRoleChange(context.Background(), &types.QueryPendingRoleChangeRequest{ + Id: args[0], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryPendingRoleChanges returns the command for querying pending role changes +func GetCmdQueryPendingRoleChanges() *cobra.Command { + cmd := &cobra.Command{ + Use: "pending-role-changes [asset_class_id] [nft_id]", + Short: "Query pending role changes, optionally filtered by registry key", + Args: cobra.RangeArgs(0, 2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + + var key *types.RegistryKey + if len(args) == 2 { + key = &types.RegistryKey{ + AssetClassId: args[0], + NftId: args[1], + } + } + + pageReq, err := client.ReadPageRequestWithPageKeyDecoded(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.PendingRoleChanges(context.Background(), &types.QueryPendingRoleChangesRequest{ + Key: key, + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "pending-role-changes") + return cmd +} diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index dd522d0189..ece3ca38c9 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "strings" "github.com/spf13/cobra" @@ -30,6 +31,8 @@ func CmdTx() *cobra.Command { CmdRevokeRole(), CmdUnregisterNFT(), CmdRegistryBulkUpdate(), + CmdProposeRoleChange(), + CmdApproveRoleChange(), ) return cmd @@ -221,3 +224,82 @@ func CmdRegistryBulkUpdate() *cobra.Command { flags.AddTxFlagsToCmd(cmd) return cmd } + +// CmdProposeRoleChange returns the command to propose a multi-party role change +func CmdProposeRoleChange() *cobra.Command { + cmd := &cobra.Command{ + Use: "propose-role-change
[address...]", + Short: "Propose a role change that accumulates approvals until the role's policy is satisfied", + Args: cobra.MinimumNArgs(5), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + role, err := types.ParseRegistryRole(args[2]) + if err != nil { + return err + } + + op, err := parseRoleChangeOperation(args[3]) + if err != nil { + return err + } + + msg := types.MsgProposeRoleChange{ + Signer: clientCtx.GetFromAddress().String(), + Key: &types.RegistryKey{ + AssetClassId: args[0], + NftId: args[1], + }, + Role: role, + Operation: op, + Addresses: args[4:], + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// CmdApproveRoleChange returns the command to approve a pending role change +func CmdApproveRoleChange() *cobra.Command { + cmd := &cobra.Command{ + Use: "approve-role-change ", + Short: "Approve a pending role change by its id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + msg := types.MsgApproveRoleChange{ + Signer: clientCtx.GetFromAddress().String(), + ChangeId: args[0], + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// parseRoleChangeOperation converts a grant/revoke string into a RoleChangeOperation. +func parseRoleChangeOperation(s string) (types.RoleChangeOperation, error) { + switch strings.ToLower(s) { + case "grant": + return types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT, nil + case "revoke": + return types.RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE, nil + default: + return types.RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED, + fmt.Errorf("invalid operation %q: must be \"grant\" or \"revoke\"", s) + } +} diff --git a/x/registry/keeper/genesis.go b/x/registry/keeper/genesis.go index f194bd02d8..6cc9059bad 100644 --- a/x/registry/keeper/genesis.go +++ b/x/registry/keeper/genesis.go @@ -14,6 +14,11 @@ func (k Keeper) InitGenesis(ctx context.Context, state *types.GenesisState) { panic(err) // Genesis should not fail } } + for _, change := range state.PendingRoleChanges { + if err := k.PendingRoleChanges.Set(ctx, change.Id, change); err != nil { + panic(err) // Genesis should not fail + } + } } func (k Keeper) ExportGenesis(ctx context.Context) *types.GenesisState { @@ -27,5 +32,13 @@ func (k Keeper) ExportGenesis(ctx context.Context) *types.GenesisState { panic(err) } + err = k.PendingRoleChanges.Walk(ctx, nil, func(_ string, value types.PendingRoleChange) (stop bool, err error) { + genesis.PendingRoleChanges = append(genesis.PendingRoleChanges, value) + return false, nil + }) + if err != nil { + panic(err) + } + return genesis } diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index c117b43236..ded128d849 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -8,6 +8,8 @@ import ( "cosmossdk.io/collections" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/provenance-io/provenance/x/registry/types" ) @@ -33,6 +35,33 @@ func (k Keeper) RemovePendingRoleChange(ctx context.Context, id string) error { return k.PendingRoleChanges.Remove(ctx, id) } +// GetPendingRoleChanges returns the pending role changes, optionally filtered to a single registry +// key. Results are paginated over the deterministic change-id keyspace. +func (k Keeper) GetPendingRoleChanges(ctx context.Context, pagination *query.PageRequest, key *types.RegistryKey) ([]types.PendingRoleChange, *query.PageResponse, error) { + ptrs, pageRes, err := query.CollectionFilteredPaginate(ctx, k.PendingRoleChanges, pagination, + func(_ string, change types.PendingRoleChange) (bool, error) { + if key == nil { + return true, nil + } + return change.Key != nil && + change.Key.AssetClassId == key.AssetClassId && + change.Key.NftId == key.NftId, nil + }, + func(_ string, change types.PendingRoleChange) (*types.PendingRoleChange, error) { + return &change, nil + }, + ) + if err != nil { + return nil, nil, err + } + + changes := make([]types.PendingRoleChange, len(ptrs)) + for i, p := range ptrs { + changes[i] = *p + } + return changes, pageRes, nil +} + // ProposeRoleChange opens (or re-uses) a pending role change and records the proposer's approval. // If the accumulated approvals already satisfy the role's policy, the change is applied // immediately. Returns the change id and whether it was applied. diff --git a/x/registry/keeper/pending_query_test.go b/x/registry/keeper/pending_query_test.go new file mode 100644 index 0000000000..f1202dc1c1 --- /dev/null +++ b/x/registry/keeper/pending_query_test.go @@ -0,0 +1,146 @@ +package keeper_test + +import ( + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/provenance-io/provenance/testutil/assertions" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// newPendingChange builds and stores a pending role change for the given key and addresses. +func (s *KeeperTestSuite) newPendingChange(key *types.RegistryKey, addresses []string) types.PendingRoleChange { + op := types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT + role := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + change := types.PendingRoleChange{ + Id: types.NewPendingRoleChangeID(key, role, op, addresses), + Key: key, + Role: role, + Operation: op, + Addresses: addresses, + Proposer: s.user1, + Approvals: []string{s.user1}, + } + s.Require().NoError(s.app.RegistryKeeper.SetPendingRoleChange(s.ctx, change)) + return change +} + +func (s *KeeperTestSuite) TestQueryPendingRoleChange() { + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + change := s.newPendingChange(key, []string{s.user2}) + + queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) + + tests := []struct { + name string + req *types.QueryPendingRoleChangeRequest + expErr string + expID string + }{ + { + name: "nil request", + req: nil, + expErr: "empty request", + }, + { + name: "empty id", + req: &types.QueryPendingRoleChangeRequest{Id: ""}, + expErr: "id cannot be empty", + }, + { + name: "not found", + req: &types.QueryPendingRoleChangeRequest{Id: "does-not-exist"}, + expErr: "pending role change", + }, + { + name: "found", + req: &types.QueryPendingRoleChangeRequest{Id: change.Id}, + expID: change.Id, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + resp, err := queryServer.PendingRoleChange(s.ctx, tc.req) + if tc.expErr != "" { + assertions.AssertErrorContents(s.T(), err, []string{tc.expErr}, "PendingRoleChange") + return + } + s.Require().NoError(err) + s.Require().NotNil(resp) + s.Require().Equal(tc.expID, resp.PendingRoleChange.Id) + }) + } +} + +func (s *KeeperTestSuite) TestQueryPendingRoleChanges() { + key1 := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + key2 := &types.RegistryKey{AssetClassId: "other-class", NftId: "other-nft"} + + c1 := s.newPendingChange(key1, []string{s.user2}) + c2 := s.newPendingChange(key2, []string{s.user2}) + + queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) + + s.Run("nil request", func() { + _, err := queryServer.PendingRoleChanges(s.ctx, nil) + assertions.AssertErrorContents(s.T(), err, []string{"empty request"}, "PendingRoleChanges") + }) + + s.Run("all changes", func() { + resp, err := queryServer.PendingRoleChanges(s.ctx, &types.QueryPendingRoleChangesRequest{}) + s.Require().NoError(err) + s.Require().Len(resp.PendingRoleChanges, 2) + ids := []string{resp.PendingRoleChanges[0].Id, resp.PendingRoleChanges[1].Id} + s.Require().ElementsMatch([]string{c1.Id, c2.Id}, ids) + }) + + s.Run("filter by key", func() { + resp, err := queryServer.PendingRoleChanges(s.ctx, &types.QueryPendingRoleChangesRequest{Key: key1}) + s.Require().NoError(err) + s.Require().Len(resp.PendingRoleChanges, 1) + s.Require().Equal(c1.Id, resp.PendingRoleChanges[0].Id) + }) + + s.Run("filter by key with no matches", func() { + empty := &types.RegistryKey{AssetClassId: "nope", NftId: "nope"} + resp, err := queryServer.PendingRoleChanges(s.ctx, &types.QueryPendingRoleChangesRequest{Key: empty}) + s.Require().NoError(err) + s.Require().Empty(resp.PendingRoleChanges) + }) + + s.Run("paginated", func() { + resp, err := queryServer.PendingRoleChanges(s.ctx, &types.QueryPendingRoleChangesRequest{ + Pagination: &query.PageRequest{Limit: 1, CountTotal: true}, + }) + s.Require().NoError(err) + s.Require().Len(resp.PendingRoleChanges, 1) + s.Require().NotNil(resp.Pagination) + s.Require().Equal(uint64(2), resp.Pagination.Total) + }) +} + +func (s *KeeperTestSuite) TestPendingRoleChangeGenesisRoundTrip() { + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + change := s.newPendingChange(key, []string{s.user2}) + + genesis := s.app.RegistryKeeper.ExportGenesis(s.ctx) + s.Require().Len(genesis.PendingRoleChanges, 1) + s.Require().Equal(change.Id, genesis.PendingRoleChanges[0].Id) + + // Clear and re-import. + s.Require().NoError(s.app.RegistryKeeper.RemovePendingRoleChange(s.ctx, change.Id)) + got, err := s.app.RegistryKeeper.GetPendingRoleChange(s.ctx, change.Id) + s.Require().NoError(err) + s.Require().Nil(got) + + s.Require().NotPanics(func() { + s.app.RegistryKeeper.InitGenesis(s.ctx, genesis) + }) + + got, err = s.app.RegistryKeeper.GetPendingRoleChange(s.ctx, change.Id) + s.Require().NoError(err) + s.Require().NotNil(got) + s.Require().Equal(change.Approvals, got.Approvals) + s.Require().Equal(change.Addresses, got.Addresses) +} diff --git a/x/registry/keeper/query_server.go b/x/registry/keeper/query_server.go index 6c1988776f..ae72ef1a42 100644 --- a/x/registry/keeper/query_server.go +++ b/x/registry/keeper/query_server.go @@ -91,3 +91,44 @@ func (qs QueryServer) HasRole(ctx context.Context, req *types.QueryHasRoleReques return &types.QueryHasRoleResponse{HasRole: hasRole}, nil } + +// PendingRoleChange returns a single pending role change by its id. +func (qs QueryServer) PendingRoleChange(ctx context.Context, req *types.QueryPendingRoleChangeRequest) (*types.QueryPendingRoleChangeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + if err := req.Validate(); err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + change, err := qs.keeper.GetPendingRoleChange(sdkCtx, req.Id) + if err != nil { + return nil, err + } + if change == nil { + return nil, types.NewErrCodePendingChangeNotFound(req.Id) + } + + return &types.QueryPendingRoleChangeResponse{PendingRoleChange: *change}, nil +} + +// PendingRoleChanges returns the pending role changes, optionally filtered by registry key. +func (qs QueryServer) PendingRoleChanges(ctx context.Context, req *types.QueryPendingRoleChangesRequest) (*types.QueryPendingRoleChangesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + if err := req.Validate(); err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + changes, pageRes, err := qs.keeper.GetPendingRoleChanges(sdkCtx, req.Pagination, req.Key) + if err != nil { + return nil, err + } + + return &types.QueryPendingRoleChangesResponse{PendingRoleChanges: changes, Pagination: pageRes}, nil +} diff --git a/x/registry/types/genesis.pb.go b/x/registry/types/genesis.pb.go index 021777c082..0f81542152 100644 --- a/x/registry/types/genesis.pb.go +++ b/x/registry/types/genesis.pb.go @@ -29,6 +29,9 @@ type GenesisState struct { // entries is the list of registry entries. // These entries define the initial state of the registry module. Entries []RegistryEntry `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries"` + // pending_role_changes is the list of role changes awaiting approval. + // These preserve in-flight multi-party role changes across genesis export/import. + PendingRoleChanges []PendingRoleChange `protobuf:"bytes,2,rep,name=pending_role_changes,json=pendingRoleChanges,proto3" json:"pending_role_changes"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -71,6 +74,13 @@ func (m *GenesisState) GetEntries() []RegistryEntry { return nil } +func (m *GenesisState) GetPendingRoleChanges() []PendingRoleChange { + if m != nil { + return m.PendingRoleChanges + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "provenance.registry.v1.GenesisState") } @@ -80,21 +90,24 @@ func init() { } var fileDescriptor_1652bcf54f1aa9cb = []byte{ - // 216 bytes of a gzipped FileDescriptorProto + // 266 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0x28, 0xca, 0x2f, 0x4b, 0xcd, 0x4b, 0xcc, 0x4b, 0x4e, 0xd5, 0x2f, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, 0x29, 0xaa, 0xd4, 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x43, 0xa8, 0xd2, 0x83, 0xa9, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x2b, 0xd1, 0x07, 0xb1, 0x20, 0xaa, 0xa5, 0x54, 0x71, 0x98, 0x09, 0xd7, 0x09, 0x56, 0xa6, - 0x14, 0xca, 0xc5, 0xe3, 0x0e, 0xb1, 0x25, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0xc8, 0x95, 0x8b, 0x3d, - 0x35, 0xaf, 0xa4, 0x28, 0x33, 0xb5, 0x58, 0x82, 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x55, 0x0f, - 0xbb, 0xb5, 0x7a, 0x41, 0x50, 0xb6, 0x6b, 0x5e, 0x49, 0x51, 0xa5, 0x13, 0xcb, 0x89, 0x7b, 0xf2, - 0x0c, 0x41, 0x30, 0xbd, 0x4e, 0xd9, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, - 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, 0xdc, 0x78, 0x2c, 0xc7, 0xc0, - 0x25, 0x99, 0x99, 0x8f, 0xc3, 0xc4, 0x00, 0xc6, 0x28, 0x93, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, - 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x84, 0x22, 0xdd, 0xcc, 0x7c, 0x24, 0x9e, 0x7e, 0x05, 0xc2, 0x3f, - 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0x60, 0xaf, 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, - 0xd9, 0xca, 0x68, 0xcf, 0x47, 0x01, 0x00, 0x00, + 0xb4, 0x83, 0x91, 0x8b, 0xc7, 0x1d, 0x62, 0x4d, 0x70, 0x49, 0x62, 0x49, 0xaa, 0x90, 0x2b, 0x17, + 0x7b, 0x6a, 0x5e, 0x49, 0x51, 0x66, 0x6a, 0xb1, 0x04, 0xa3, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0xaa, + 0x1e, 0x76, 0x7b, 0xf5, 0x82, 0xa0, 0x6c, 0xd7, 0xbc, 0x92, 0xa2, 0x4a, 0x27, 0x96, 0x13, 0xf7, + 0xe4, 0x19, 0x82, 0x60, 0x7a, 0x85, 0x12, 0xb9, 0x44, 0x0a, 0x52, 0xf3, 0x52, 0x32, 0xf3, 0xd2, + 0xe3, 0x8b, 0xf2, 0x73, 0x52, 0xe3, 0x93, 0x33, 0x12, 0xf3, 0xd2, 0x53, 0x8b, 0x25, 0x98, 0xc0, + 0x66, 0x6a, 0xe2, 0x32, 0x33, 0x00, 0xa2, 0x27, 0x28, 0x3f, 0x27, 0xd5, 0x19, 0xac, 0x03, 0x6a, + 0xae, 0x50, 0x01, 0xba, 0x44, 0xb1, 0x53, 0xf6, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, + 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, + 0x31, 0x70, 0x49, 0x66, 0xe6, 0xe3, 0xb0, 0x20, 0x80, 0x31, 0xca, 0x24, 0x3d, 0xb3, 0x24, 0xa3, + 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xa1, 0x48, 0x37, 0x33, 0x1f, 0x89, 0xa7, 0x5f, 0x81, + 0x08, 0xb3, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0x70, 0x70, 0x19, 0x03, 0x02, 0x00, 0x00, + 0xff, 0xff, 0x72, 0x54, 0x76, 0x01, 0xab, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -117,6 +130,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.PendingRoleChanges) > 0 { + for iNdEx := len(m.PendingRoleChanges) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PendingRoleChanges[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } if len(m.Entries) > 0 { for iNdEx := len(m.Entries) - 1; iNdEx >= 0; iNdEx-- { { @@ -157,6 +184,12 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + if len(m.PendingRoleChanges) > 0 { + for _, e := range m.PendingRoleChanges { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -229,6 +262,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingRoleChanges", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PendingRoleChanges = append(m.PendingRoleChanges, PendingRoleChange{}) + if err := m.PendingRoleChanges[len(m.PendingRoleChanges)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index ddd9242f06..ecd30eb431 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -238,6 +238,23 @@ func (m QueryHasRoleRequest) Validate() error { return errors.Join(errs...) } +// Validate validates the QueryPendingRoleChangeRequest +func (m QueryPendingRoleChangeRequest) Validate() error { + if len(m.Id) == 0 { + return NewErrCodeInvalidField("id", "id cannot be empty") + } + return nil +} + +// Validate validates the QueryPendingRoleChangesRequest +func (m QueryPendingRoleChangesRequest) Validate() error { + // The key is optional; validate it only when provided. + if m.Key != nil { + return m.Key.Validate() + } + return nil +} + // ValidateStringLength checks several conditions in order: // 1. length is not between the minimum and maximum length returns an ErrCodeInvalidField error. // 2. contains whitespace returns an ErrCodeInvalidField error. diff --git a/x/registry/types/query.pb.go b/x/registry/types/query.pb.go index 54b86d6402..b468252d01 100644 --- a/x/registry/types/query.pb.go +++ b/x/registry/types/query.pb.go @@ -355,6 +355,211 @@ func (m *QueryHasRoleResponse) GetHasRole() bool { return false } +// QueryPendingRoleChangeRequest is the request type for the Query/PendingRoleChange RPC method. +type QueryPendingRoleChangeRequest struct { + // id is the deterministic identifier of the pending role change to retrieve. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` +} + +func (m *QueryPendingRoleChangeRequest) Reset() { *m = QueryPendingRoleChangeRequest{} } +func (m *QueryPendingRoleChangeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPendingRoleChangeRequest) ProtoMessage() {} +func (*QueryPendingRoleChangeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{6} +} +func (m *QueryPendingRoleChangeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingRoleChangeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingRoleChangeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingRoleChangeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingRoleChangeRequest.Merge(m, src) +} +func (m *QueryPendingRoleChangeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingRoleChangeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingRoleChangeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingRoleChangeRequest proto.InternalMessageInfo + +func (m *QueryPendingRoleChangeRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +// QueryPendingRoleChangeResponse is the response type for the Query/PendingRoleChange RPC method. +type QueryPendingRoleChangeResponse struct { + // pending_role_change is the pending role change for the requested id. + PendingRoleChange PendingRoleChange `protobuf:"bytes,1,opt,name=pending_role_change,json=pendingRoleChange,proto3" json:"pending_role_change"` +} + +func (m *QueryPendingRoleChangeResponse) Reset() { *m = QueryPendingRoleChangeResponse{} } +func (m *QueryPendingRoleChangeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPendingRoleChangeResponse) ProtoMessage() {} +func (*QueryPendingRoleChangeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{7} +} +func (m *QueryPendingRoleChangeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingRoleChangeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingRoleChangeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingRoleChangeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingRoleChangeResponse.Merge(m, src) +} +func (m *QueryPendingRoleChangeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingRoleChangeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingRoleChangeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingRoleChangeResponse proto.InternalMessageInfo + +func (m *QueryPendingRoleChangeResponse) GetPendingRoleChange() PendingRoleChange { + if m != nil { + return m.PendingRoleChange + } + return PendingRoleChange{} +} + +// QueryPendingRoleChangesRequest is the paginated request type for the Query/PendingRoleChanges +// RPC method. +type QueryPendingRoleChangesRequest struct { + // key optionally filters the results to a single registry entry. When unset, all pending role + // changes are returned. + Key *RegistryKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,99,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPendingRoleChangesRequest) Reset() { *m = QueryPendingRoleChangesRequest{} } +func (m *QueryPendingRoleChangesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryPendingRoleChangesRequest) ProtoMessage() {} +func (*QueryPendingRoleChangesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{8} +} +func (m *QueryPendingRoleChangesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingRoleChangesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingRoleChangesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingRoleChangesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingRoleChangesRequest.Merge(m, src) +} +func (m *QueryPendingRoleChangesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingRoleChangesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingRoleChangesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingRoleChangesRequest proto.InternalMessageInfo + +func (m *QueryPendingRoleChangesRequest) GetKey() *RegistryKey { + if m != nil { + return m.Key + } + return nil +} + +func (m *QueryPendingRoleChangesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryPendingRoleChangesResponse is the paginated response type for the Query/PendingRoleChanges +// RPC method. +type QueryPendingRoleChangesResponse struct { + // pending_role_changes is the collection of pending role changes. + PendingRoleChanges []PendingRoleChange `protobuf:"bytes,1,rep,name=pending_role_changes,json=pendingRoleChanges,proto3" json:"pending_role_changes"` + // pagination is the pagination details for this response. + Pagination *query.PageResponse `protobuf:"bytes,99,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryPendingRoleChangesResponse) Reset() { *m = QueryPendingRoleChangesResponse{} } +func (m *QueryPendingRoleChangesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryPendingRoleChangesResponse) ProtoMessage() {} +func (*QueryPendingRoleChangesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{9} +} +func (m *QueryPendingRoleChangesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryPendingRoleChangesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryPendingRoleChangesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryPendingRoleChangesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryPendingRoleChangesResponse.Merge(m, src) +} +func (m *QueryPendingRoleChangesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryPendingRoleChangesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryPendingRoleChangesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryPendingRoleChangesResponse proto.InternalMessageInfo + +func (m *QueryPendingRoleChangesResponse) GetPendingRoleChanges() []PendingRoleChange { + if m != nil { + return m.PendingRoleChanges + } + return nil +} + +func (m *QueryPendingRoleChangesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + func init() { proto.RegisterType((*QueryGetRegistryRequest)(nil), "provenance.registry.v1.QueryGetRegistryRequest") proto.RegisterType((*QueryGetRegistryResponse)(nil), "provenance.registry.v1.QueryGetRegistryResponse") @@ -362,6 +567,10 @@ func init() { proto.RegisterType((*QueryGetRegistriesResponse)(nil), "provenance.registry.v1.QueryGetRegistriesResponse") proto.RegisterType((*QueryHasRoleRequest)(nil), "provenance.registry.v1.QueryHasRoleRequest") proto.RegisterType((*QueryHasRoleResponse)(nil), "provenance.registry.v1.QueryHasRoleResponse") + proto.RegisterType((*QueryPendingRoleChangeRequest)(nil), "provenance.registry.v1.QueryPendingRoleChangeRequest") + proto.RegisterType((*QueryPendingRoleChangeResponse)(nil), "provenance.registry.v1.QueryPendingRoleChangeResponse") + proto.RegisterType((*QueryPendingRoleChangesRequest)(nil), "provenance.registry.v1.QueryPendingRoleChangesRequest") + proto.RegisterType((*QueryPendingRoleChangesResponse)(nil), "provenance.registry.v1.QueryPendingRoleChangesResponse") } func init() { @@ -369,45 +578,55 @@ func init() { } var fileDescriptor_c166c561e401a2eb = []byte{ - // 605 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x94, 0x3d, 0x6f, 0xd3, 0x40, - 0x18, 0xc7, 0x73, 0x6d, 0xa1, 0xe5, 0x0a, 0x1d, 0x8e, 0x0a, 0x5c, 0x0b, 0x99, 0xc8, 0xb4, 0x50, - 0xf1, 0xe2, 0xc3, 0x01, 0x24, 0xe6, 0xf0, 0x12, 0xa0, 0x4b, 0x30, 0x62, 0x81, 0x21, 0xba, 0x38, - 0x87, 0x63, 0x25, 0xf5, 0xb9, 0xbe, 0x4b, 0x84, 0x15, 0x65, 0x61, 0x63, 0x43, 0xe2, 0x0b, 0xf4, - 0x0b, 0xb0, 0x30, 0xb3, 0xb0, 0x75, 0xac, 0xc4, 0xc2, 0x84, 0x50, 0xc2, 0x07, 0x41, 0x3e, 0x9f, - 0xf3, 0x02, 0x09, 0x4e, 0xc5, 0xe6, 0x7b, 0xee, 0xf9, 0x3f, 0xcf, 0xef, 0x79, 0x39, 0x43, 0x33, - 0x8c, 0x58, 0x97, 0x06, 0x24, 0x70, 0x29, 0x8e, 0xa8, 0xe7, 0x73, 0x11, 0xc5, 0xb8, 0x6b, 0xe3, - 0x83, 0x0e, 0x8d, 0x62, 0x2b, 0x8c, 0x98, 0x60, 0xe8, 0xc2, 0xd8, 0xc7, 0xca, 0x7c, 0xac, 0xae, - 0xad, 0x5f, 0x77, 0x19, 0xdf, 0x67, 0x1c, 0xd7, 0x09, 0xa7, 0xa9, 0x00, 0x77, 0xed, 0x3a, 0x15, - 0xc4, 0xc6, 0x21, 0xf1, 0xfc, 0x80, 0x08, 0x9f, 0x05, 0x69, 0x0c, 0x7d, 0xd3, 0x63, 0x1e, 0x93, - 0x9f, 0x38, 0xf9, 0x52, 0xd6, 0x4b, 0x1e, 0x63, 0x5e, 0x9b, 0x62, 0x12, 0xfa, 0x98, 0x04, 0x01, - 0x13, 0x52, 0xc2, 0xd5, 0xed, 0xce, 0x1c, 0xb6, 0x11, 0x83, 0x74, 0x33, 0xab, 0xf0, 0xe2, 0xf3, - 0x24, 0x79, 0x85, 0x0a, 0x47, 0xdd, 0x38, 0xf4, 0xa0, 0x43, 0xb9, 0x40, 0xf7, 0xe0, 0x72, 0x8b, - 0xc6, 0x1a, 0x28, 0x82, 0xdd, 0xf5, 0xd2, 0x15, 0x6b, 0x76, 0x1d, 0x56, 0xa6, 0xda, 0xa3, 0xb1, - 0x93, 0xf8, 0x9b, 0x2e, 0xd4, 0xfe, 0x8e, 0xc8, 0x43, 0x16, 0x70, 0x8a, 0x2a, 0x70, 0x2d, 0xd3, - 0xaa, 0xb8, 0x3b, 0x79, 0x71, 0x1f, 0x05, 0x22, 0x8a, 0xcb, 0x2b, 0x47, 0x3f, 0x2e, 0x17, 0x9c, - 0x91, 0xd8, 0x7c, 0x0f, 0xe0, 0xd6, 0x1f, 0x59, 0x7c, 0xca, 0x33, 0xf2, 0x6d, 0xb8, 0x41, 0x38, - 0xa7, 0xa2, 0xe6, 0xb6, 0x09, 0xe7, 0x35, 0xbf, 0x21, 0x93, 0x9d, 0x71, 0xce, 0x4a, 0xeb, 0x83, - 0xc4, 0xf8, 0xb4, 0x81, 0x1e, 0x43, 0x38, 0xee, 0xb4, 0xe6, 0x4a, 0x9c, 0xab, 0x56, 0x3a, 0x16, - 0x2b, 0x19, 0x8b, 0x95, 0xce, 0x51, 0x8d, 0xc5, 0xaa, 0x12, 0x8f, 0xaa, 0x0c, 0xce, 0x84, 0xd2, - 0xfc, 0x0c, 0xa0, 0x3e, 0x8b, 0x45, 0xd5, 0xbc, 0x07, 0x61, 0x34, 0xb2, 0x6a, 0xa0, 0xb8, 0x7c, - 0xd2, 0xaa, 0x27, 0xe4, 0xa8, 0x32, 0x83, 0xf9, 0x5a, 0x2e, 0x73, 0x4a, 0x32, 0x05, 0x7d, 0x08, - 0xe0, 0x79, 0x09, 0xfd, 0x84, 0x70, 0x87, 0xb5, 0xe9, 0xff, 0x0d, 0x1d, 0x69, 0x70, 0x95, 0x34, - 0x1a, 0x11, 0xe5, 0x5c, 0x5b, 0x92, 0xad, 0xce, 0x8e, 0xe8, 0x3e, 0x5c, 0x89, 0x58, 0x9b, 0x6a, - 0xcb, 0x45, 0xb0, 0xbb, 0x51, 0xda, 0xce, 0x8b, 0x28, 0x59, 0xa4, 0xc2, 0xb4, 0xe1, 0xe6, 0x34, - 0xa1, 0x6a, 0xe8, 0x16, 0x5c, 0x6b, 0x12, 0x5e, 0x93, 0x51, 0x13, 0xce, 0x35, 0x67, 0xb5, 0x99, - 0xba, 0x94, 0x3e, 0xad, 0xc0, 0x53, 0x52, 0x83, 0xbe, 0x00, 0xb8, 0x3e, 0xb1, 0x81, 0x08, 0xcf, - 0x4b, 0x3c, 0x67, 0xfb, 0xf5, 0xdb, 0x8b, 0x0b, 0x52, 0x2e, 0xf3, 0xd9, 0xbb, 0x6f, 0xbf, 0x3e, - 0x2e, 0x3d, 0x44, 0x65, 0x9c, 0xf3, 0xf4, 0x70, 0xaf, 0x45, 0x63, 0x6b, 0x7a, 0x43, 0xfb, 0xa9, - 0x31, 0x78, 0x23, 0x92, 0x03, 0x3a, 0x04, 0xf0, 0xdc, 0xd4, 0x3a, 0x21, 0x7b, 0x41, 0x9e, 0xf1, - 0x33, 0xd0, 0x4b, 0x27, 0x91, 0xa8, 0x22, 0x76, 0x65, 0x11, 0x26, 0x2a, 0xe6, 0x15, 0x81, 0xbe, - 0x02, 0xb8, 0xaa, 0x46, 0x83, 0x6e, 0xfc, 0x33, 0xd3, 0xf4, 0x8a, 0xe9, 0x37, 0x17, 0x73, 0x56, - 0x40, 0xaf, 0x25, 0xd0, 0x4b, 0xf4, 0x62, 0x1e, 0x50, 0xb6, 0x0b, 0xf9, 0x5d, 0xc5, 0x3d, 0xb5, - 0x94, 0x7d, 0xdc, 0x4b, 0x14, 0xfd, 0x72, 0xeb, 0x68, 0x60, 0x80, 0xe3, 0x81, 0x01, 0x7e, 0x0e, - 0x0c, 0xf0, 0x61, 0x68, 0x14, 0x8e, 0x87, 0x46, 0xe1, 0xfb, 0xd0, 0x28, 0xc0, 0x2d, 0x9f, 0xcd, - 0xc1, 0xac, 0x82, 0x57, 0x77, 0x3d, 0x5f, 0x34, 0x3b, 0x75, 0xcb, 0x65, 0xfb, 0x13, 0x54, 0xb7, - 0x7c, 0x36, 0xc9, 0xf8, 0x76, 0x4c, 0x29, 0xe2, 0x90, 0xf2, 0xfa, 0x69, 0xf9, 0xc7, 0xbd, 0xf3, - 0x3b, 0x00, 0x00, 0xff, 0xff, 0xb3, 0x7a, 0x07, 0x40, 0x36, 0x06, 0x00, 0x00, + // 760 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0xcf, 0x4f, 0x13, 0x41, + 0x14, 0xc7, 0x3b, 0x05, 0x2d, 0x3e, 0x94, 0x84, 0x81, 0x68, 0x69, 0xb4, 0x34, 0x2b, 0x28, 0x2a, + 0xee, 0xd0, 0x0a, 0xca, 0x19, 0x54, 0x54, 0x2e, 0x75, 0x8d, 0x17, 0x3d, 0x34, 0xd3, 0x76, 0xdc, + 0x6e, 0x28, 0x3b, 0xcb, 0xce, 0xd2, 0xd8, 0x34, 0x1c, 0xf4, 0xe6, 0xcd, 0xc4, 0x3f, 0x40, 0xce, + 0x1e, 0xbd, 0xea, 0xc5, 0x1b, 0x17, 0x13, 0x12, 0x2f, 0x9e, 0x8c, 0x01, 0xff, 0x10, 0xb3, 0xb3, + 0xd3, 0x5f, 0xb4, 0x4b, 0xd9, 0xc0, 0xad, 0x3b, 0x33, 0xdf, 0x79, 0x9f, 0xef, 0x7b, 0x6f, 0x5e, + 0x0a, 0x9a, 0xe3, 0xf2, 0x1a, 0xb3, 0xa9, 0x5d, 0x62, 0xc4, 0x65, 0xa6, 0x25, 0x3c, 0xb7, 0x4e, + 0x6a, 0x59, 0xb2, 0xb5, 0xcd, 0xdc, 0xba, 0xee, 0xb8, 0xdc, 0xe3, 0xf8, 0x72, 0xfb, 0x8c, 0xde, + 0x3c, 0xa3, 0xd7, 0xb2, 0xa9, 0xdb, 0x25, 0x2e, 0x36, 0xb9, 0x20, 0x45, 0x2a, 0x58, 0x20, 0x20, + 0xb5, 0x6c, 0x91, 0x79, 0x34, 0x4b, 0x1c, 0x6a, 0x5a, 0x36, 0xf5, 0x2c, 0x6e, 0x07, 0x77, 0xa4, + 0x26, 0x4d, 0x6e, 0x72, 0xf9, 0x93, 0xf8, 0xbf, 0xd4, 0xea, 0x55, 0x93, 0x73, 0xb3, 0xca, 0x08, + 0x75, 0x2c, 0x42, 0x6d, 0x9b, 0x7b, 0x52, 0x22, 0xd4, 0xee, 0x6c, 0x08, 0x5b, 0x8b, 0x41, 0x1e, + 0xd3, 0xf2, 0x70, 0xe5, 0xb9, 0x1f, 0x7c, 0x8d, 0x79, 0x86, 0xda, 0x31, 0xd8, 0xd6, 0x36, 0x13, + 0x1e, 0x5e, 0x82, 0xa1, 0x0d, 0x56, 0x4f, 0xa2, 0x0c, 0x9a, 0x1b, 0xcd, 0x5d, 0xd7, 0xfb, 0xfb, + 0xd0, 0x9b, 0xaa, 0x75, 0x56, 0x37, 0xfc, 0xf3, 0x5a, 0x09, 0x92, 0xbd, 0x37, 0x0a, 0x87, 0xdb, + 0x82, 0xe1, 0x35, 0x18, 0x69, 0x6a, 0xd5, 0xbd, 0xb3, 0x83, 0xee, 0x7d, 0x64, 0x7b, 0x6e, 0x7d, + 0x65, 0x78, 0xef, 0xcf, 0x74, 0xcc, 0x68, 0x89, 0xb5, 0x0f, 0x08, 0xa6, 0x8e, 0x44, 0xb1, 0x98, + 0x68, 0x92, 0xcf, 0xc0, 0x18, 0x15, 0x82, 0x79, 0x85, 0x52, 0x95, 0x0a, 0x51, 0xb0, 0xca, 0x32, + 0xd8, 0x05, 0xe3, 0xa2, 0x5c, 0x5d, 0xf5, 0x17, 0x9f, 0x96, 0xf1, 0x63, 0x80, 0x76, 0xa6, 0x93, + 0x25, 0x89, 0x73, 0x43, 0x0f, 0xca, 0xa2, 0xfb, 0x65, 0xd1, 0x83, 0x3a, 0xaa, 0xb2, 0xe8, 0x79, + 0x6a, 0x32, 0x15, 0xc1, 0xe8, 0x50, 0x6a, 0x5f, 0x11, 0xa4, 0xfa, 0xb1, 0x28, 0xcf, 0xeb, 0x00, + 0x6e, 0x6b, 0x35, 0x89, 0x32, 0x43, 0x51, 0x5d, 0x77, 0xc8, 0xf1, 0x5a, 0x1f, 0xe6, 0x9b, 0x03, + 0x99, 0x03, 0x92, 0x2e, 0xe8, 0x5d, 0x04, 0x13, 0x12, 0xfa, 0x09, 0x15, 0x06, 0xaf, 0xb2, 0xd3, + 0x15, 0x1d, 0x27, 0x21, 0x41, 0xcb, 0x65, 0x97, 0x09, 0x91, 0x8c, 0xcb, 0x54, 0x37, 0x3f, 0xf1, + 0x32, 0x0c, 0xbb, 0xbc, 0xca, 0x92, 0x43, 0x19, 0x34, 0x37, 0x96, 0x9b, 0x19, 0x74, 0xa3, 0x64, + 0x91, 0x0a, 0x2d, 0x0b, 0x93, 0xdd, 0x84, 0x2a, 0xa1, 0x53, 0x30, 0x52, 0xa1, 0xa2, 0x20, 0x6f, + 0xf5, 0x39, 0x47, 0x8c, 0x44, 0x25, 0x38, 0xa2, 0x11, 0xb8, 0x26, 0x25, 0x79, 0x66, 0x97, 0x2d, + 0xdb, 0xf4, 0xd7, 0x56, 0x2b, 0xd4, 0x6e, 0xd5, 0x0d, 0x8f, 0x41, 0xbc, 0xd5, 0x0d, 0x71, 0xab, + 0xac, 0xbd, 0x43, 0x90, 0x0e, 0x53, 0xa8, 0x70, 0x05, 0x98, 0x70, 0x82, 0x4d, 0x19, 0xb2, 0x50, + 0x92, 0xdb, 0x2a, 0x43, 0xb7, 0xc2, 0xfc, 0xf4, 0xdc, 0xa7, 0x8a, 0x39, 0xee, 0x1c, 0xdd, 0xd0, + 0x3e, 0x87, 0x32, 0x88, 0x53, 0x56, 0xe5, 0xac, 0x3a, 0xfc, 0x27, 0x82, 0xe9, 0x50, 0x42, 0x95, + 0x26, 0x0a, 0x93, 0x7d, 0xd2, 0xd4, 0x6c, 0xf8, 0xc8, 0x79, 0xc2, 0x3d, 0x79, 0x3a, 0xbb, 0xe6, + 0xcf, 0x7d, 0x49, 0xc0, 0x39, 0xe9, 0x07, 0x7f, 0x47, 0x30, 0xda, 0x31, 0xa8, 0x30, 0x09, 0xe3, + 0x0c, 0x19, 0x92, 0xa9, 0x85, 0x93, 0x0b, 0x02, 0x10, 0xed, 0xd9, 0xfb, 0x5f, 0xff, 0x3e, 0xc5, + 0x1f, 0xe2, 0x15, 0x32, 0x60, 0x42, 0x93, 0xc6, 0x06, 0xab, 0xeb, 0xdd, 0x83, 0x6c, 0x27, 0x58, + 0xb4, 0xdf, 0x78, 0xfe, 0x07, 0xde, 0x45, 0x70, 0xa9, 0x6b, 0xea, 0xe0, 0xec, 0x09, 0x79, 0xda, + 0xd3, 0x32, 0x95, 0x8b, 0x22, 0x51, 0x26, 0xe6, 0xa4, 0x09, 0x0d, 0x67, 0x06, 0x99, 0xc0, 0x3f, + 0x10, 0x24, 0xd4, 0x0b, 0xc6, 0x77, 0x8e, 0x8d, 0xd4, 0x3d, 0x89, 0x52, 0xf3, 0x27, 0x3b, 0xac, + 0x80, 0x5e, 0x4b, 0xa0, 0x97, 0xf8, 0x45, 0x18, 0x50, 0x73, 0x64, 0x0c, 0xce, 0x2a, 0x69, 0xa8, + 0xd9, 0xb5, 0x43, 0x1a, 0xbe, 0x62, 0xc7, 0xef, 0x92, 0xf1, 0x9e, 0x46, 0xc5, 0x4b, 0xc7, 0x02, + 0x86, 0x8d, 0xa0, 0xd4, 0xfd, 0xa8, 0x32, 0xe5, 0x70, 0x59, 0x3a, 0xcc, 0xe1, 0x85, 0x30, 0x87, + 0x7d, 0x9e, 0x1f, 0x69, 0xf8, 0x5d, 0xf2, 0x0d, 0x01, 0xee, 0x7d, 0xb9, 0x38, 0x22, 0x48, 0xab, + 0x5f, 0x1e, 0x44, 0xd6, 0x29, 0x07, 0x8b, 0xd2, 0x81, 0x8e, 0xe7, 0x23, 0x38, 0x10, 0x2b, 0x1b, + 0x7b, 0x07, 0x69, 0xb4, 0x7f, 0x90, 0x46, 0x7f, 0x0f, 0xd2, 0xe8, 0xe3, 0x61, 0x3a, 0xb6, 0x7f, + 0x98, 0x8e, 0xfd, 0x3e, 0x4c, 0xc7, 0x60, 0xca, 0xe2, 0x21, 0x28, 0x79, 0xf4, 0x6a, 0xd1, 0xb4, + 0xbc, 0xca, 0x76, 0x51, 0x2f, 0xf1, 0xcd, 0x8e, 0x70, 0x77, 0x2d, 0xde, 0x19, 0xfc, 0x6d, 0x3b, + 0xbc, 0x57, 0x77, 0x98, 0x28, 0x9e, 0x97, 0xff, 0x8a, 0xee, 0xfd, 0x0f, 0x00, 0x00, 0xff, 0xff, + 0xdb, 0x88, 0x32, 0x7c, 0xda, 0x09, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -430,6 +649,10 @@ type QueryClient interface { GetRegistries(ctx context.Context, in *QueryGetRegistriesRequest, opts ...grpc.CallOption) (*QueryGetRegistriesResponse, error) // HasRole returns true if the address has the specified role for the given key. HasRole(ctx context.Context, in *QueryHasRoleRequest, opts ...grpc.CallOption) (*QueryHasRoleResponse, error) + // PendingRoleChange returns a single pending role change by its id. + PendingRoleChange(ctx context.Context, in *QueryPendingRoleChangeRequest, opts ...grpc.CallOption) (*QueryPendingRoleChangeResponse, error) + // PendingRoleChanges returns the pending role changes, optionally filtered by registry key. + PendingRoleChanges(ctx context.Context, in *QueryPendingRoleChangesRequest, opts ...grpc.CallOption) (*QueryPendingRoleChangesResponse, error) } type queryClient struct { @@ -467,6 +690,24 @@ func (c *queryClient) HasRole(ctx context.Context, in *QueryHasRoleRequest, opts return out, nil } +func (c *queryClient) PendingRoleChange(ctx context.Context, in *QueryPendingRoleChangeRequest, opts ...grpc.CallOption) (*QueryPendingRoleChangeResponse, error) { + out := new(QueryPendingRoleChangeResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Query/PendingRoleChange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) PendingRoleChanges(ctx context.Context, in *QueryPendingRoleChangesRequest, opts ...grpc.CallOption) (*QueryPendingRoleChangesResponse, error) { + out := new(QueryPendingRoleChangesResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Query/PendingRoleChanges", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // GetRegistry returns the registry entry for a given key. @@ -477,6 +718,10 @@ type QueryServer interface { GetRegistries(context.Context, *QueryGetRegistriesRequest) (*QueryGetRegistriesResponse, error) // HasRole returns true if the address has the specified role for the given key. HasRole(context.Context, *QueryHasRoleRequest) (*QueryHasRoleResponse, error) + // PendingRoleChange returns a single pending role change by its id. + PendingRoleChange(context.Context, *QueryPendingRoleChangeRequest) (*QueryPendingRoleChangeResponse, error) + // PendingRoleChanges returns the pending role changes, optionally filtered by registry key. + PendingRoleChanges(context.Context, *QueryPendingRoleChangesRequest) (*QueryPendingRoleChangesResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -492,6 +737,12 @@ func (*UnimplementedQueryServer) GetRegistries(ctx context.Context, req *QueryGe func (*UnimplementedQueryServer) HasRole(ctx context.Context, req *QueryHasRoleRequest) (*QueryHasRoleResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method HasRole not implemented") } +func (*UnimplementedQueryServer) PendingRoleChange(ctx context.Context, req *QueryPendingRoleChangeRequest) (*QueryPendingRoleChangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PendingRoleChange not implemented") +} +func (*UnimplementedQueryServer) PendingRoleChanges(ctx context.Context, req *QueryPendingRoleChangesRequest) (*QueryPendingRoleChangesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method PendingRoleChanges not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -551,6 +802,42 @@ func _Query_HasRole_Handler(srv interface{}, ctx context.Context, dec func(inter return interceptor(ctx, in, info, handler) } +func _Query_PendingRoleChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPendingRoleChangeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PendingRoleChange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Query/PendingRoleChange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PendingRoleChange(ctx, req.(*QueryPendingRoleChangeRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_PendingRoleChanges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryPendingRoleChangesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).PendingRoleChanges(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Query/PendingRoleChanges", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).PendingRoleChanges(ctx, req.(*QueryPendingRoleChangesRequest)) + } + return interceptor(ctx, in, info, handler) +} + var Query_serviceDesc = _Query_serviceDesc var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Query", @@ -568,6 +855,14 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "HasRole", Handler: _Query_HasRole_Handler, }, + { + MethodName: "PendingRoleChange", + Handler: _Query_PendingRoleChange_Handler, + }, + { + MethodName: "PendingRoleChanges", + Handler: _Query_PendingRoleChanges_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/query.proto", @@ -816,81 +1111,244 @@ func (m *QueryHasRoleResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *QueryPendingRoleChangeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *QueryGetRegistryRequest) Size() (n int) { - if m == nil { - return 0 - } + +func (m *QueryPendingRoleChangeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingRoleChangeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if m.Key != nil { - l = m.Key.Size() - n += 1 + l + sovQuery(uint64(l)) + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0xa } - return n + return len(dAtA) - i, nil } -func (m *QueryGetRegistryResponse) Size() (n int) { - if m == nil { - return 0 +func (m *QueryPendingRoleChangeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - l = m.Registry.Size() - n += 1 + l + sovQuery(uint64(l)) - return n + return dAtA[:n], nil } -func (m *QueryGetRegistriesRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AssetClassId) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - if m.Pagination != nil { - l = m.Pagination.Size() - n += 2 + l + sovQuery(uint64(l)) - } - return n +func (m *QueryPendingRoleChangeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryGetRegistriesResponse) Size() (n int) { - if m == nil { - return 0 - } +func (m *QueryPendingRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Registries) > 0 { - for _, e := range m.Registries { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) + { + size, err := m.PendingRoleChange.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) } - if m.Pagination != nil { - l = m.Pagination.Size() - n += 2 + l + sovQuery(uint64(l)) - } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *QueryHasRoleRequest) Size() (n int) { - if m == nil { - return 0 - } +func (m *QueryPendingRoleChangesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPendingRoleChangesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingRoleChangesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6 + i-- + dAtA[i] = 0x9a + } + if m.Key != nil { + { + size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryPendingRoleChangesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryPendingRoleChangesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryPendingRoleChangesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6 + i-- + dAtA[i] = 0x9a + } + if len(m.PendingRoleChanges) > 0 { + for iNdEx := len(m.PendingRoleChanges) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PendingRoleChanges[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryGetRegistryRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryGetRegistryResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Registry.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryGetRegistriesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryGetRegistriesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Registries) > 0 { + for _, e := range m.Registries { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryHasRoleRequest) Size() (n int) { + if m == nil { + return 0 + } var l int _ = l if m.Key != nil { @@ -919,6 +1377,66 @@ func (m *QueryHasRoleResponse) Size() (n int) { return n } +func (m *QueryPendingRoleChangeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPendingRoleChangeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.PendingRoleChange.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryPendingRoleChangesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryPendingRoleChangesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.PendingRoleChanges) > 0 { + for _, e := range m.PendingRoleChanges { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1539,6 +2057,413 @@ func (m *QueryHasRoleResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryPendingRoleChangeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingRoleChangeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingRoleChangeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPendingRoleChangeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingRoleChangeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingRoleChangeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingRoleChange", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PendingRoleChange.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPendingRoleChangesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingRoleChangesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingRoleChangesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Key == nil { + m.Key = &RegistryKey{} + } + if err := m.Key.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 99: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryPendingRoleChangesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryPendingRoleChangesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryPendingRoleChangesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingRoleChanges", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PendingRoleChanges = append(m.PendingRoleChanges, PendingRoleChange{}) + if err := m.PendingRoleChanges[len(m.PendingRoleChanges)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 99: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/query.pb.gw.go b/x/registry/types/query.pb.gw.go index d54e834457..9a048cdd5f 100644 --- a/x/registry/types/query.pb.gw.go +++ b/x/registry/types/query.pb.gw.go @@ -307,6 +307,96 @@ func local_request_Query_HasRole_0(ctx context.Context, marshaler runtime.Marsha } +func request_Query_PendingRoleChange_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingRoleChangeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := client.PendingRoleChange(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PendingRoleChange_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingRoleChangeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) + } + + msg, err := server.PendingRoleChange(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_PendingRoleChanges_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_PendingRoleChanges_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingRoleChangesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PendingRoleChanges_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.PendingRoleChanges(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_PendingRoleChanges_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryPendingRoleChangesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_PendingRoleChanges_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.PendingRoleChanges(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -382,6 +472,52 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_PendingRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PendingRoleChange_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingRoleChange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PendingRoleChanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_PendingRoleChanges_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingRoleChanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -483,6 +619,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_PendingRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PendingRoleChange_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingRoleChange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_PendingRoleChanges_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_PendingRoleChanges_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_PendingRoleChanges_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -492,6 +668,10 @@ var ( pattern_Query_GetRegistries_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 1}, []string{"provenance", "registry", "v1"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_HasRole_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5, 1, 0, 4, 1, 5, 6, 1, 0, 4, 1, 5, 7}, []string{"provenance", "registry", "v1", "has_role", "key.asset_class_id", "key.nft_id", "address", "role"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PendingRoleChange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"provenance", "registry", "v1", "pending_role_change", "id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_PendingRoleChanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "pending_role_changes"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -500,4 +680,8 @@ var ( forward_Query_GetRegistries_0 = runtime.ForwardResponseMessage forward_Query_HasRole_0 = runtime.ForwardResponseMessage + + forward_Query_PendingRoleChange_0 = runtime.ForwardResponseMessage + + forward_Query_PendingRoleChanges_0 = runtime.ForwardResponseMessage ) From dea47f52081b90f9d06af78e9ea17ff1d1e74046 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 18 Jun 2026 15:35:15 -0600 Subject: [PATCH 06/36] Add Controller revoke, invalidation, and edge-case acceptance tests Complete the Option B acceptance matrix: - Scenario C: Controller REVOKE accumulation (sole-controller revoke applies on one approval; with a Secured Party set, revoke requires current controller + secured party) - Invalidation-by-re-resolution: after the controller is rotated out from under a pending change, the stale approval no longer counts; the live current controller must approve for the change to apply - Edge cases: duplicate approval is idempotent, approving a non-existent change errors, and a registry deleted underneath cleans up the orphaned pending change Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ole_change_accumulation_acceptance_test.go | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index 98999da563..e1b8e3219d 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -113,6 +113,20 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) approve(changeID, signer str return resp.Applied } +// proposeRevokeController opens a pending REVOKE of the CONTROLLER role from addr, signed by +// proposer, and returns the change id and whether it applied immediately. +func (s *RoleChangeAccumulationAcceptanceTestSuite) proposeRevokeController(key *types.RegistryKey, addr, proposer string) (string, bool) { + resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: proposer, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Operation: types.RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE, + Addresses: []string{addr}, + }) + s.Require().NoError(err) + return resp.ChangeId, resp.Applied +} + func (s *RoleChangeAccumulationAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { entry, err := s.registryKeeper.GetRegistry(s.ctx, key) s.Require().NoError(err) @@ -298,3 +312,132 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_Accumul s.Require().NoError(err) s.Require().Nil(pending, "pending change should be removed after it applies") } + +// --- Scenario C: Controller revoke accumulation --------------------------------------- + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerRevoke_Accumulation() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + s.Run("single controller, no secured party: applies on proposer approval", func() { + key := s.mintNFT("acc-c-single") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + }) + + _, applied := s.proposeRevokeController(key, s.currentController, s.currentController) + s.Require().True(applied, "the sole current controller's approval satisfies a revoke") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.currentController) + }) + + s.Run("secured party set: requires current controller and secured party", func() { + key := s.mintNFT("acc-c-with-sp") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + changeID, applied := s.proposeRevokeController(key, s.currentController, s.currentController) + s.Require().False(applied, "secured party approval still required for a revoke") + s.Require().Contains(s.roleAddresses(key, controllerRole), s.currentController) + + applied = s.approve(changeID, s.securedParty) + s.Require().True(applied, "current controller + secured party satisfy the revoke") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.currentController) + }) +} + +// --- Invalidation: stale approvals are voided when role holders change underneath ------ +// +// The accumulation engine re-resolves roles against live registry state on every approval, so an +// approval recorded by a party that is no longer the current role holder cannot satisfy the policy. +// This is the ticket's invalidation guarantee without a separate expiry mechanism. + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_InvalidationOnRoleChange() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + rotatedController := genAddr() + + key := s.mintNFT("acc-invalidation") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + }) + + // Current controller proposes; only their approval is recorded so far. + changeID, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied) + + // The controller is rotated out from under the pending change (e.g. via a separate completed + // flow). The pending change still carries the now-stale currentController approval. + s.Require().NoError(s.registryKeeper.RevokeRole(s.ctx, key, controllerRole, []string{s.currentController})) + s.Require().NoError(s.registryKeeper.GrantRole(s.ctx, key, controllerRole, []string{rotatedController})) + + // The new controller approving is no longer enough: the stale approval no longer counts as the + // current controller, so the policy is not satisfied. + applied = s.approve(changeID, s.newController) + s.Require().False(applied, "stale current-controller approval is voided after the role holder changed") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + + // Once the live current controller approves, the policy is satisfied and the change applies. + applied = s.approve(changeID, rotatedController) + s.Require().True(applied, "the live current controller's approval re-satisfies the policy") + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController) +} + +// --- Edge cases ----------------------------------------------------------------------- + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestPendingChange_EdgeCases() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + s.Run("duplicate approval is idempotent", func() { + key := s.mintNFT("acc-edge-dup") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + changeID, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied) + + // The current controller approving again must not double-count or apply the change. + applied = s.approve(changeID, s.currentController) + s.Require().False(applied) + + pending, err := s.registryKeeper.GetPendingRoleChange(s.ctx, changeID) + s.Require().NoError(err) + s.Require().NotNil(pending) + s.Require().Equal([]string{s.currentController}, pending.Approvals, "approver recorded exactly once") + }) + + s.Run("approving a non-existent change errors", func() { + _, err := s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{ + Signer: s.currentController, + ChangeId: "does-not-exist", + }) + s.Require().Error(err) + s.Require().Contains(err.Error(), "pending role change") + }) + + s.Run("registry removed underneath cleans up the pending change", func() { + key := s.mintNFT("acc-edge-removed") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + changeID, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied) + + s.Require().NoError(s.registryKeeper.DeleteRegistry(s.ctx, key)) + + _, err := s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{ + Signer: s.securedParty, + ChangeId: changeID, + }) + s.Require().Error(err) + + pending, err := s.registryKeeper.GetPendingRoleChange(s.ctx, changeID) + s.Require().NoError(err) + s.Require().Nil(pending, "the orphaned pending change is removed") + }) +} From ddb43acff2e658e762b6ea2fc285e69ad045d43f Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 09:27:17 -0600 Subject: [PATCH 07/36] reproduce behavior of SetRoles --- proto/provenance/registry/v1/events.proto | 6 +- proto/provenance/registry/v1/registry.proto | 40 +- proto/provenance/registry/v1/tx.proto | 28 +- x/registry/client/cli/tx.go | 61 ++- x/registry/keeper/msg_server.go | 2 +- x/registry/keeper/pending.go | 93 ++-- x/registry/keeper/pending_query_test.go | 20 +- ...ole_change_accumulation_acceptance_test.go | 125 ++++- x/registry/types/events.go | 4 - x/registry/types/events.pb.go | 258 +--------- x/registry/types/msgs.go | 23 +- x/registry/types/msgs_test.go | 18 +- x/registry/types/pending.go | 31 +- x/registry/types/registry.pb.go | 474 +++++++++++------- x/registry/types/tx.pb.go | 453 ++++------------- 15 files changed, 690 insertions(+), 946 deletions(-) diff --git a/proto/provenance/registry/v1/events.proto b/proto/provenance/registry/v1/events.proto index 2482dc3e2a..4bd5c5abc3 100644 --- a/proto/provenance/registry/v1/events.proto +++ b/proto/provenance/registry/v1/events.proto @@ -44,9 +44,7 @@ message EventRoleChangeProposed { string nft_id = 1; string asset_class_id = 2; string change_id = 3; - string role = 4; - string operation = 5; - string proposer = 6; + string proposer = 4; } // EventRoleChangeApproved is emitted when a party records an approval for a pending role change. @@ -60,6 +58,4 @@ message EventRoleChangeApplied { string nft_id = 1; string asset_class_id = 2; string change_id = 3; - string role = 4; - string operation = 5; } diff --git a/proto/provenance/registry/v1/registry.proto b/proto/provenance/registry/v1/registry.proto index 1dd8001221..12e9328a9f 100644 --- a/proto/provenance/registry/v1/registry.proto +++ b/proto/provenance/registry/v1/registry.proto @@ -85,39 +85,35 @@ message RolesEntry { repeated string addresses = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } -// RoleChangeOperation identifies the kind of role change a pending change applies. -enum RoleChangeOperation { - // ROLE_CHANGE_OPERATION_UNSPECIFIED is the default, invalid operation. - ROLE_CHANGE_OPERATION_UNSPECIFIED = 0; - // ROLE_CHANGE_OPERATION_GRANT adds the target addresses to the role when applied. - ROLE_CHANGE_OPERATION_GRANT = 1; - // ROLE_CHANGE_OPERATION_REVOKE removes the target addresses from the role when applied. - ROLE_CHANGE_OPERATION_REVOKE = 2; +// RoleUpdate specifies the desired set of addresses for a single role. +message RoleUpdate { + // role is the role to update. + RegistryRole role = 1; + + // addresses is the desired set of addresses for this role. + // An empty list means the role should be cleared entirely. + repeated string addresses = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } -// PendingRoleChange is a role change awaiting the approvals required by the role's authorization -// policy. Each required party submits a single-signer approval; once the accumulated approvers -// satisfy the policy, the change is applied and this record is removed. +// PendingRoleChange is a role change awaiting the approvals required by the affected roles' +// authorization policies. Each required party submits a single-signer approval; once the +// accumulated approvers satisfy every affected role's policy, the change is applied atomically +// and this record is removed. message PendingRoleChange { // id is the deterministic identifier of this pending change - // (a hash of key + role + operation + sorted addresses). + // (a hash of key + the sorted set of role updates). string id = 1; // key identifies the registry entry the change applies to. RegistryKey key = 2; - // role is the role being changed. - RegistryRole role = 3; - - // operation is the kind of change to apply (grant or revoke) once approved. - RoleChangeOperation operation = 4; - - // addresses is the set of addresses the operation applies to. - repeated string addresses = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // role_updates is the desired state for each role the change will set when applied. The same + // desired-state, multi-role semantics as MsgSetRoles, collected through accumulated approvals. + repeated RoleUpdate role_updates = 3 [(gogoproto.nullable) = false]; // proposer is the address that opened the pending change. - string proposer = 6 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + string proposer = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // approvals is the accumulated set of addresses that have approved this change. - repeated string approvals = 7 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + repeated string approvals = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"]; } \ No newline at end of file diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index fd61d4522f..f0541530e0 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -187,40 +187,26 @@ message MsgSetRoles { repeated RoleUpdate role_updates = 3 [(gogoproto.nullable) = false]; } -// RoleUpdate specifies the desired set of addresses for a single role. -message RoleUpdate { - // role is the role to update. - RegistryRole role = 1; - - // addresses is the desired set of addresses for this role. - // An empty list means the role should be cleared entirely. - repeated string addresses = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"]; -} - // MsgSetRolesResponse defines the response for SetRoles. message MsgSetRolesResponse {} // MsgProposeRoleChange opens a pending role change that collects single-signer approvals until -// the role's authorization policy is satisfied. Using single-signer messages (rather than one -// multi-signer message) keeps every step compatible with authz delegation. +// every affected role's authorization policy is satisfied, then applies the whole batch +// atomically. Its payload mirrors MsgSetRoles (desired-state, multi-role), but using single-signer +// messages (rather than one multi-signer message) keeps every step compatible with authz delegation. message MsgProposeRoleChange { option (cosmos.msg.v1.signer) = "signer"; // signer is the address proposing the change. It is recorded as the first approval; it only - // counts toward the policy if the signer is itself a required party. + // counts toward a policy if the signer is itself a required party. string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // key identifies the registry entry to change. RegistryKey key = 2; - // role is the role being changed. - RegistryRole role = 3; - - // operation is the kind of change to apply once approved (grant or revoke). - RoleChangeOperation operation = 4; - - // addresses is the set of addresses the operation applies to. - repeated string addresses = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // role_updates contains the desired state for each role to set when the change is applied. + // At least one entry is required. Roles not listed here are unchanged. + repeated RoleUpdate role_updates = 3 [(gogoproto.nullable) = false]; } // MsgProposeRoleChangeResponse defines the response for ProposeRoleChange. diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index ece3ca38c9..b8a14cae1b 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -228,21 +228,23 @@ func CmdRegistryBulkUpdate() *cobra.Command { // CmdProposeRoleChange returns the command to propose a multi-party role change func CmdProposeRoleChange() *cobra.Command { cmd := &cobra.Command{ - Use: "propose-role-change
[address...]", - Short: "Propose a role change that accumulates approvals until the role's policy is satisfied", - Args: cobra.MinimumNArgs(5), + Use: "propose-role-change [role=address[,address...]...]", + Short: "Propose a desired-state role change that accumulates approvals until every affected role's policy is satisfied", + Long: `Propose a batch of desired-state role updates that accumulates single-signer approvals until +every affected role's authorization policy is satisfied, then applies atomically. + +Each role update is "role=address[,address...]". An empty address list clears the role. + +Example: + propose-role-change myclass nft1 controller=cosmos1new secured_party_enote=`, + Args: cobra.MinimumNArgs(3), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } - role, err := types.ParseRegistryRole(args[2]) - if err != nil { - return err - } - - op, err := parseRoleChangeOperation(args[3]) + roleUpdates, err := parseRoleUpdates(args[2:]) if err != nil { return err } @@ -253,9 +255,7 @@ func CmdProposeRoleChange() *cobra.Command { AssetClassId: args[0], NftId: args[1], }, - Role: role, - Operation: op, - Addresses: args[4:], + RoleUpdates: roleUpdates, } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) @@ -291,15 +291,32 @@ func CmdApproveRoleChange() *cobra.Command { return cmd } -// parseRoleChangeOperation converts a grant/revoke string into a RoleChangeOperation. -func parseRoleChangeOperation(s string) (types.RoleChangeOperation, error) { - switch strings.ToLower(s) { - case "grant": - return types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT, nil - case "revoke": - return types.RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE, nil - default: - return types.RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED, - fmt.Errorf("invalid operation %q: must be \"grant\" or \"revoke\"", s) +// parseRoleUpdates converts "role=address[,address...]" arguments into RoleUpdate values. +// An empty address list (e.g. "role=") clears the role. +func parseRoleUpdates(args []string) ([]types.RoleUpdate, error) { + updates := make([]types.RoleUpdate, 0, len(args)) + for _, arg := range args { + name, addrCSV, ok := strings.Cut(arg, "=") + if !ok { + return nil, fmt.Errorf("invalid role update %q: expected format \"role=address[,address...]\"", arg) + } + + role, err := types.ParseRegistryRole(name) + if err != nil { + return nil, err + } + + var addresses []string + if trimmed := strings.TrimSpace(addrCSV); trimmed != "" { + for _, addr := range strings.Split(trimmed, ",") { + addr = strings.TrimSpace(addr) + if addr != "" { + addresses = append(addresses, addr) + } + } + } + + updates = append(updates, types.RoleUpdate{Role: role, Addresses: addresses}) } + return updates, nil } diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 329efd0659..d95783da88 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -152,7 +152,7 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types // ProposeRoleChange opens a pending role change that accumulates single-signer approvals and // auto-applies once the role's authorization policy is satisfied. func (k msgServer) ProposeRoleChange(ctx context.Context, msg *types.MsgProposeRoleChange) (*types.MsgProposeRoleChangeResponse, error) { - id, applied, err := k.Keeper.ProposeRoleChange(ctx, msg.Signer, msg.Key, msg.Role, msg.Operation, msg.Addresses) + id, applied, err := k.Keeper.ProposeRoleChange(ctx, msg.Signer, msg.Key, msg.RoleUpdates) if err != nil { return nil, err } diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index ded128d849..e54293268f 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -3,7 +3,6 @@ package keeper import ( "context" "errors" - "fmt" "slices" "cosmossdk.io/collections" @@ -62,10 +61,11 @@ func (k Keeper) GetPendingRoleChanges(ctx context.Context, pagination *query.Pag return changes, pageRes, nil } -// ProposeRoleChange opens (or re-uses) a pending role change and records the proposer's approval. -// If the accumulated approvals already satisfy the role's policy, the change is applied -// immediately. Returns the change id and whether it was applied. -func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *types.RegistryKey, role types.RegistryRole, op types.RoleChangeOperation, addresses []string) (string, bool, error) { +// ProposeRoleChange opens (or re-uses) a pending role change for a batch of desired-state role +// updates and records the proposer's approval. If the accumulated approvals already satisfy every +// affected role's policy, the change is applied immediately. Returns the change id and whether it +// was applied. +func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *types.RegistryKey, roleUpdates []types.RoleUpdate) (string, bool, error) { entry, err := k.GetRegistry(ctx, key) if err != nil { return "", false, err @@ -74,13 +74,7 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ return "", false, types.NewErrCodeRegistryNotFound(key.String()) } - if _, ok := types.RoleAuthorizationMap()[role]; !ok { - return "", false, types.NewErrCodeUnauthorized( - fmt.Sprintf("role %s has no authorization policy; propose/approve flow requires a policy-governed role", role.ShortString()), - ) - } - - id := types.NewPendingRoleChangeID(key, role, op, addresses) + id := types.NewPendingRoleChangeID(key, roleUpdates) change, err := k.GetPendingRoleChange(ctx, id) if err != nil { @@ -88,12 +82,10 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ } if change == nil { change = &types.PendingRoleChange{ - Id: id, - Key: key, - Role: role, - Operation: op, - Addresses: addresses, - Proposer: proposer, + Id: id, + Key: key, + RoleUpdates: roleUpdates, + Proposer: proposer, } k.EmitEvent(ctx, types.NewEventRoleChangeProposed(change)) } @@ -157,30 +149,59 @@ func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.Re return true, nil } -// pendingChangeSatisfied reports whether the accumulated approvals satisfy the role's policy. +// pendingChangeSatisfied reports whether the accumulated approvals satisfy every affected role's +// authorization policy. A role with no policy falls back to NFT ownership: at least one approver +// must own the NFT. The change applies only when all role updates are satisfied (atomic gate). func (k Keeper) pendingChangeSatisfied(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange) bool { - roleAuth, ok := types.RoleAuthorizationMap()[change.Role] - if !ok { - return false + roleAuths := types.RoleAuthorizationMap() + for _, update := range change.RoleUpdates { + roleAuth, ok := roleAuths[update.Role] + if !ok { + // Legacy fallback: at least one approver must own the NFT. + if !k.anyApproverOwnsNFT(ctx, change) { + return false + } + continue + } + + // Only addresses being newly added to the role are relevant for ASSIGNMENT_NEW checks. + newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) + if k.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, newAddrs, change.Approvals) != nil { + return false + } } + return true +} - // New addresses are only meaningful for grant operations (ASSIGNMENT_NEW checks). - var newAddrs []string - if change.Operation == types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT { - newAddrs = change.Addresses +// anyApproverOwnsNFT reports whether any accumulated approver owns the change's NFT. +func (k Keeper) anyApproverOwnsNFT(ctx context.Context, change *types.PendingRoleChange) bool { + for _, approver := range change.Approvals { + if k.ValidateNFTOwner(ctx, &change.Key.AssetClassId, &change.Key.NftId, approver) == nil { + return true + } } + return false +} - return k.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, newAddrs, change.Approvals) == nil +// additions returns the addresses in desired that are not already present in current. +func additions(current, desired []string) []string { + if len(current) == 0 { + return desired + } + have := make(map[string]bool, len(current)) + for _, a := range current { + have[a] = true + } + var added []string + for _, a := range desired { + if !have[a] { + added = append(added, a) + } + } + return added } -// applyPendingChange performs the role grant or revoke described by the change. +// applyPendingChange applies the batch of role updates as a single atomic desired-state set. func (k Keeper) applyPendingChange(ctx context.Context, change *types.PendingRoleChange) error { - switch change.Operation { - case types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT: - return k.GrantRole(ctx, change.Key, change.Role, change.Addresses) - case types.RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE: - return k.RevokeRole(ctx, change.Key, change.Role, change.Addresses) - default: - return types.NewErrCodeInvalidField("operation", "unsupported operation %s", change.Operation.ShortString()) - } + return k.SetRoles(ctx, change.Key, change.RoleUpdates) } diff --git a/x/registry/keeper/pending_query_test.go b/x/registry/keeper/pending_query_test.go index f1202dc1c1..4a9e4fa8d8 100644 --- a/x/registry/keeper/pending_query_test.go +++ b/x/registry/keeper/pending_query_test.go @@ -10,16 +10,16 @@ import ( // newPendingChange builds and stores a pending role change for the given key and addresses. func (s *KeeperTestSuite) newPendingChange(key *types.RegistryKey, addresses []string) types.PendingRoleChange { - op := types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT - role := types.RegistryRole_REGISTRY_ROLE_CONTROLLER - change := types.PendingRoleChange{ - Id: types.NewPendingRoleChangeID(key, role, op, addresses), - Key: key, - Role: role, - Operation: op, + roleUpdates := []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: addresses, - Proposer: s.user1, - Approvals: []string{s.user1}, + }} + change := types.PendingRoleChange{ + Id: types.NewPendingRoleChangeID(key, roleUpdates), + Key: key, + RoleUpdates: roleUpdates, + Proposer: s.user1, + Approvals: []string{s.user1}, } s.Require().NoError(s.app.RegistryKeeper.SetPendingRoleChange(s.ctx, change)) return change @@ -142,5 +142,5 @@ func (s *KeeperTestSuite) TestPendingRoleChangeGenesisRoundTrip() { s.Require().NoError(err) s.Require().NotNil(got) s.Require().Equal(change.Approvals, got.Approvals) - s.Require().Equal(change.Addresses, got.Addresses) + s.Require().Equal(change.RoleUpdates, got.RoleUpdates) } diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index e1b8e3219d..3a483168f4 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -93,11 +93,12 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) setupRegistry(key *types.Reg // proposer, and returns the change id. func (s *RoleChangeAccumulationAcceptanceTestSuite) proposeNewController(key *types.RegistryKey, proposer string) (string, bool) { resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ - Signer: proposer, - Key: key, - Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, - Operation: types.RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT, - Addresses: []string{s.newController}, + Signer: proposer, + Key: key, + RoleUpdates: []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: []string{s.newController}, + }}, }) s.Require().NoError(err) return resp.ChangeId, resp.Applied @@ -113,15 +114,24 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) approve(changeID, signer str return resp.Applied } -// proposeRevokeController opens a pending REVOKE of the CONTROLLER role from addr, signed by -// proposer, and returns the change id and whether it applied immediately. +// proposeRevokeController opens a pending REVOKE of the CONTROLLER role from addr by proposing an +// empty desired-state for the role, signed by proposer, and returns the change id and whether it +// applied immediately. func (s *RoleChangeAccumulationAcceptanceTestSuite) proposeRevokeController(key *types.RegistryKey, addr, proposer string) (string, bool) { + current := s.roleAddresses(key, types.RegistryRole_REGISTRY_ROLE_CONTROLLER) + desired := make([]string, 0, len(current)) + for _, a := range current { + if a != addr { + desired = append(desired, a) + } + } resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ - Signer: proposer, - Key: key, - Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, - Operation: types.RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE, - Addresses: []string{addr}, + Signer: proposer, + Key: key, + RoleUpdates: []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: desired, + }}, }) s.Require().NoError(err) return resp.ChangeId, resp.Applied @@ -441,3 +451,94 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestPendingChange_EdgeCases( s.Require().Nil(pending, "the orphaned pending change is removed") }) } + +// proposeBatch opens a pending change for an arbitrary batch of desired-state role updates, signed +// by proposer, and returns the change id and whether it applied immediately. +func (s *RoleChangeAccumulationAcceptanceTestSuite) proposeBatch(key *types.RegistryKey, proposer string, updates []types.RoleUpdate) (string, bool) { + resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: proposer, + Key: key, + RoleUpdates: updates, + }) + s.Require().NoError(err) + return resp.ChangeId, resp.Applied +} + +// --- Atomic grant+revoke: controller rotation in a single pending change --------------- +// +// A controller rotation (remove the old controller, add the new one) is expressed as a single +// desired-state role update. The grant and revoke are inseparable: the whole change applies +// atomically once the role's policy is satisfied, never leaving the role half-updated. + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerRotation_AtomicGrantRevoke() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + key := s.mintNFT("acc-rotation") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + // Desired state replaces the controller entirely: drops currentController, adds newController. + rotation := []types.RoleUpdate{{Role: controllerRole, Addresses: []string{s.newController}}} + + changeID, applied := s.proposeBatch(key, s.currentController, rotation) + s.Require().False(applied) + + applied = s.approve(changeID, s.securedParty) + s.Require().False(applied, "still missing the new controller's approval") + + // Nothing is half-applied while pending: the old controller is still present. + s.Require().Contains(s.roleAddresses(key, controllerRole), s.currentController) + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + + applied = s.approve(changeID, s.newController) + s.Require().True(applied, "current controller + secured party + new controller satisfy the rotation") + + // The grant and revoke applied together: role is now exactly the new controller. + s.Require().Equal([]string{s.newController}, s.roleAddresses(key, controllerRole)) +} + +// --- Atomic batch across roles: policy role + NFT-owner-fallback role ------------------ +// +// A single pending change can carry updates to several roles. It applies only when every affected +// role's gate is satisfied (policy for governed roles, NFT ownership for the rest), and all updates +// apply together. + +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestBatchRoleUpdate_AcrossRoles_AtomicApply() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + servicerRole := types.RegistryRole_REGISTRY_ROLE_SERVICER + oldServicer := genAddr() + newServicer := genAddr() + + key := s.mintNFT("acc-batch-roles") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: servicerRole, Addresses: []string{oldServicer}}, + }) + + // One batch: rotate the controller (policy-governed) and replace the servicer (no policy, so it + // falls back to NFT ownership). + batch := []types.RoleUpdate{ + {Role: controllerRole, Addresses: []string{s.newController}}, + {Role: servicerRole, Addresses: []string{newServicer}}, + } + + changeID, applied := s.proposeBatch(key, s.currentController, batch) + s.Require().False(applied) + + // Both controller approvals are present (no secured party set), but the servicer update still + // needs the NFT owner's approval, so the atomic batch must NOT apply. + applied = s.approve(changeID, s.newController) + s.Require().False(applied, "servicer update still requires the NFT owner's approval") + s.Require().Equal([]string{s.currentController}, s.roleAddresses(key, controllerRole)) + s.Require().Equal([]string{oldServicer}, s.roleAddresses(key, servicerRole)) + + // The NFT owner approves, satisfying the servicer (owner-fallback) gate. Now every gate is met + // and the whole batch applies atomically. + applied = s.approve(changeID, s.nftOwner) + s.Require().True(applied, "all gates satisfied: controller policy and servicer owner-fallback") + s.Require().Equal([]string{s.newController}, s.roleAddresses(key, controllerRole)) + s.Require().Equal([]string{newServicer}, s.roleAddresses(key, servicerRole)) +} diff --git a/x/registry/types/events.go b/x/registry/types/events.go index f2e6bb00ef..6a1f4d489d 100644 --- a/x/registry/types/events.go +++ b/x/registry/types/events.go @@ -55,8 +55,6 @@ func NewEventRoleChangeProposed(change *PendingRoleChange) *EventRoleChangePropo NftId: change.Key.NftId, AssetClassId: change.Key.AssetClassId, ChangeId: change.Id, - Role: change.Role.ShortString(), - Operation: change.Operation.ShortString(), Proposer: change.Proposer, } } @@ -75,8 +73,6 @@ func NewEventRoleChangeApplied(change *PendingRoleChange) *EventRoleChangeApplie NftId: change.Key.NftId, AssetClassId: change.Key.AssetClassId, ChangeId: change.Id, - Role: change.Role.ShortString(), - Operation: change.Operation.ShortString(), } } diff --git a/x/registry/types/events.pb.go b/x/registry/types/events.pb.go index 6d255c81a7..0503d0c4c5 100644 --- a/x/registry/types/events.pb.go +++ b/x/registry/types/events.pb.go @@ -324,9 +324,7 @@ type EventRoleChangeProposed struct { NftId string `protobuf:"bytes,1,opt,name=nft_id,json=nftId,proto3" json:"nft_id,omitempty"` AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` ChangeId string `protobuf:"bytes,3,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` - Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` - Operation string `protobuf:"bytes,5,opt,name=operation,proto3" json:"operation,omitempty"` - Proposer string `protobuf:"bytes,6,opt,name=proposer,proto3" json:"proposer,omitempty"` + Proposer string `protobuf:"bytes,4,opt,name=proposer,proto3" json:"proposer,omitempty"` } func (m *EventRoleChangeProposed) Reset() { *m = EventRoleChangeProposed{} } @@ -383,20 +381,6 @@ func (m *EventRoleChangeProposed) GetChangeId() string { return "" } -func (m *EventRoleChangeProposed) GetRole() string { - if m != nil { - return m.Role - } - return "" -} - -func (m *EventRoleChangeProposed) GetOperation() string { - if m != nil { - return m.Operation - } - return "" -} - func (m *EventRoleChangeProposed) GetProposer() string { if m != nil { return m.Proposer @@ -462,8 +446,6 @@ type EventRoleChangeApplied struct { NftId string `protobuf:"bytes,1,opt,name=nft_id,json=nftId,proto3" json:"nft_id,omitempty"` AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` ChangeId string `protobuf:"bytes,3,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` - Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` - Operation string `protobuf:"bytes,5,opt,name=operation,proto3" json:"operation,omitempty"` } func (m *EventRoleChangeApplied) Reset() { *m = EventRoleChangeApplied{} } @@ -520,20 +502,6 @@ func (m *EventRoleChangeApplied) GetChangeId() string { return "" } -func (m *EventRoleChangeApplied) GetRole() string { - if m != nil { - return m.Role - } - return "" -} - -func (m *EventRoleChangeApplied) GetOperation() string { - if m != nil { - return m.Operation - } - return "" -} - func init() { proto.RegisterType((*EventNFTRegistered)(nil), "provenance.registry.v1.EventNFTRegistered") proto.RegisterType((*EventRoleGranted)(nil), "provenance.registry.v1.EventRoleGranted") @@ -550,33 +518,31 @@ func init() { } var fileDescriptor_61a0995529587ff0 = []byte{ - // 406 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x94, 0xcf, 0xae, 0xd2, 0x40, - 0x18, 0xc5, 0x19, 0xf9, 0x13, 0x3a, 0x31, 0xc6, 0x4c, 0x14, 0x2b, 0x9a, 0x86, 0x54, 0x17, 0x6c, - 0x6c, 0x43, 0xf4, 0x05, 0x84, 0xa8, 0x61, 0x63, 0xb0, 0x4a, 0x4c, 0xdc, 0x90, 0xa1, 0xfd, 0x80, - 0x86, 0x3a, 0x33, 0x99, 0x19, 0x1a, 0x59, 0xfa, 0x06, 0x3e, 0x84, 0x2f, 0xe2, 0xce, 0x25, 0x4b, - 0x97, 0x06, 0x5e, 0xe4, 0xa6, 0x53, 0xa0, 0xfc, 0xb9, 0x3b, 0x6e, 0x6e, 0xee, 0xae, 0x73, 0xbe, - 0x93, 0x93, 0xf3, 0xfd, 0xda, 0x0e, 0x7e, 0x21, 0x24, 0x4f, 0x81, 0x51, 0x16, 0x82, 0x2f, 0x61, - 0x1a, 0x2b, 0x2d, 0x97, 0x7e, 0xda, 0xf1, 0x21, 0x05, 0xa6, 0x95, 0x27, 0x24, 0xd7, 0x9c, 0x34, - 0x0a, 0x93, 0xb7, 0x33, 0x79, 0x69, 0xc7, 0xfd, 0x84, 0xc9, 0xbb, 0xcc, 0xf7, 0xf1, 0xfd, 0x97, - 0xc0, 0xc8, 0x20, 0x21, 0x22, 0x8f, 0x71, 0x8d, 0x4d, 0xf4, 0x28, 0x8e, 0x6c, 0xd4, 0x42, 0x6d, - 0x2b, 0xa8, 0xb2, 0x89, 0xee, 0x47, 0xe4, 0x25, 0x7e, 0x40, 0x95, 0x02, 0x3d, 0x0a, 0x13, 0xaa, - 0x54, 0x36, 0xbe, 0x67, 0xc6, 0xf7, 0x8d, 0xda, 0xcb, 0xc4, 0x7e, 0xe4, 0xfe, 0x44, 0xf8, 0xa1, - 0xc9, 0x0c, 0x78, 0x02, 0x1f, 0x24, 0x65, 0xfa, 0xc2, 0x44, 0x42, 0x70, 0x45, 0xf2, 0x04, 0xec, - 0xb2, 0x99, 0x99, 0x67, 0xf2, 0x1c, 0x5b, 0x34, 0x8a, 0x24, 0x28, 0x05, 0xca, 0xae, 0xb4, 0xca, - 0x6d, 0x2b, 0x28, 0x84, 0xe3, 0x0e, 0x01, 0xa4, 0x7c, 0x7e, 0xfb, 0x1d, 0x3e, 0xe3, 0x47, 0x3b, - 0xb4, 0x43, 0x26, 0x6f, 0x08, 0xee, 0x57, 0x6c, 0xe7, 0x7b, 0x6d, 0xdf, 0x61, 0x77, 0x91, 0xcc, - 0x87, 0x22, 0xa2, 0x97, 0x32, 0x76, 0xff, 0x20, 0xfc, 0x64, 0x4f, 0xac, 0x37, 0xa3, 0x6c, 0x0a, - 0x03, 0xc9, 0x05, 0x57, 0x97, 0x82, 0x7b, 0x86, 0xad, 0xd0, 0xc4, 0x65, 0x86, 0x9c, 0x5e, 0x3d, - 0x17, 0x0e, 0xa8, 0x56, 0x8e, 0xa9, 0x72, 0x01, 0x92, 0xea, 0x98, 0x33, 0xbb, 0x6a, 0x06, 0x85, - 0x40, 0x9a, 0xb8, 0x2e, 0xf2, 0x5e, 0xd2, 0xae, 0xe5, 0x69, 0xbb, 0xb3, 0x1b, 0x9c, 0xad, 0xf0, - 0x56, 0x98, 0xef, 0xfe, 0xa4, 0x05, 0x3a, 0x69, 0xd1, 0xc4, 0x75, 0x9a, 0x1b, 0xe5, 0x76, 0x85, - 0xfd, 0xd9, 0xfd, 0x8d, 0x70, 0xe3, 0x3c, 0x34, 0x89, 0xef, 0x16, 0x96, 0xee, 0xfc, 0xef, 0xda, - 0x41, 0xab, 0xb5, 0x83, 0xfe, 0xaf, 0x1d, 0xf4, 0x6b, 0xe3, 0x94, 0x56, 0x1b, 0xa7, 0xf4, 0x6f, - 0xe3, 0x94, 0xf0, 0xd3, 0x98, 0x7b, 0xd7, 0xff, 0xfc, 0x03, 0xf4, 0xed, 0xcd, 0x34, 0xd6, 0xb3, - 0xc5, 0xd8, 0x0b, 0xf9, 0x77, 0xbf, 0x30, 0xbd, 0x8a, 0xf9, 0xc1, 0xc9, 0xff, 0x51, 0x5c, 0x2b, - 0x7a, 0x29, 0x40, 0x8d, 0x6b, 0xe6, 0x4e, 0x79, 0x7d, 0x15, 0x00, 0x00, 0xff, 0xff, 0xbf, 0x08, - 0x35, 0x24, 0x7a, 0x04, 0x00, 0x00, + // 382 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x53, 0xcd, 0x4e, 0xea, 0x40, + 0x18, 0x65, 0x2e, 0x5c, 0x42, 0x27, 0x37, 0x37, 0x37, 0x93, 0x2b, 0xd6, 0x9f, 0x34, 0xa4, 0xba, + 0x60, 0x63, 0x1b, 0xa2, 0x2f, 0x20, 0x44, 0x0d, 0x1b, 0x83, 0x55, 0x62, 0xe2, 0x86, 0x0c, 0x9d, + 0x01, 0x1a, 0xea, 0x4c, 0x33, 0x33, 0x34, 0xb2, 0xf4, 0x09, 0xf4, 0xb1, 0x5c, 0xb2, 0x74, 0x69, + 0xe0, 0x45, 0x4c, 0xa7, 0x40, 0x55, 0xdc, 0xa1, 0xee, 0xfa, 0x9d, 0x73, 0x72, 0x7a, 0xbe, 0xd3, + 0x7e, 0x70, 0x2f, 0x12, 0x3c, 0xa6, 0x0c, 0x33, 0x9f, 0xba, 0x82, 0xf6, 0x03, 0xa9, 0xc4, 0xd8, + 0x8d, 0x6b, 0x2e, 0x8d, 0x29, 0x53, 0xd2, 0x89, 0x04, 0x57, 0x1c, 0x95, 0x33, 0x91, 0xb3, 0x10, + 0x39, 0x71, 0xcd, 0xbe, 0x80, 0xe8, 0x24, 0xd1, 0x9d, 0x9f, 0x5e, 0x79, 0x1a, 0xa6, 0x82, 0x12, + 0xb4, 0x01, 0x8b, 0xac, 0xa7, 0x3a, 0x01, 0x31, 0x41, 0x05, 0x54, 0x0d, 0xef, 0x37, 0xeb, 0xa9, + 0x26, 0x41, 0xfb, 0xf0, 0x2f, 0x96, 0x92, 0xaa, 0x8e, 0x1f, 0x62, 0x29, 0x13, 0xfa, 0x97, 0xa6, + 0xff, 0x68, 0xb4, 0x91, 0x80, 0x4d, 0x62, 0xdf, 0x03, 0xf8, 0x4f, 0x7b, 0x7a, 0x3c, 0xa4, 0x67, + 0x02, 0x33, 0xb5, 0xa6, 0x23, 0x42, 0xb0, 0x20, 0x78, 0x48, 0xcd, 0xbc, 0xe6, 0xf4, 0x33, 0xda, + 0x85, 0x06, 0x26, 0x44, 0x50, 0x29, 0xa9, 0x34, 0x0b, 0x95, 0x7c, 0xd5, 0xf0, 0x32, 0xe0, 0x7d, + 0x06, 0x8f, 0xc6, 0x7c, 0xf8, 0xf3, 0x19, 0x2e, 0xe1, 0xff, 0x45, 0xb5, 0x6d, 0x26, 0xbe, 0xa8, + 0xdc, 0x6b, 0x68, 0xa6, 0x7b, 0xcd, 0xbf, 0x61, 0x7d, 0x14, 0x0e, 0xdb, 0x11, 0xc1, 0xeb, 0x76, + 0x6c, 0x3f, 0x00, 0xb8, 0xb9, 0x6c, 0xac, 0x31, 0xc0, 0xac, 0x4f, 0x5b, 0x82, 0x47, 0x5c, 0xae, + 0x5b, 0xdc, 0x0e, 0x34, 0x7c, 0x6d, 0x97, 0x08, 0xd2, 0xf6, 0x4a, 0x29, 0xd0, 0x24, 0x68, 0x1b, + 0x96, 0xa2, 0xf4, 0x2d, 0xc2, 0x2c, 0xa4, 0xdc, 0x62, 0xb6, 0xbd, 0x95, 0x40, 0xc7, 0x91, 0xfe, + 0x8b, 0x3f, 0x78, 0x82, 0x55, 0x4f, 0x9c, 0x0a, 0xc5, 0x3c, 0xd0, 0x72, 0xb6, 0x05, 0x2c, 0xaf, + 0x7a, 0x86, 0xc1, 0x77, 0xee, 0x58, 0x1f, 0x3e, 0x4d, 0x2d, 0x30, 0x99, 0x5a, 0xe0, 0x65, 0x6a, + 0x81, 0xc7, 0x99, 0x95, 0x9b, 0xcc, 0xac, 0xdc, 0xf3, 0xcc, 0xca, 0xc1, 0xad, 0x80, 0x3b, 0x9f, + 0xdf, 0x65, 0x0b, 0xdc, 0x1c, 0xf5, 0x03, 0x35, 0x18, 0x75, 0x1d, 0x9f, 0xdf, 0xba, 0x99, 0xe8, + 0x20, 0xe0, 0x6f, 0x26, 0xf7, 0x2e, 0xbb, 0x78, 0x35, 0x8e, 0xa8, 0xec, 0x16, 0xf5, 0xb9, 0x1f, + 0xbe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x6a, 0x22, 0x48, 0x15, 0x04, 0x00, 0x00, } func (m *EventNFTRegistered) Marshal() (dAtA []byte, err error) { @@ -821,20 +787,6 @@ func (m *EventRoleChangeProposed) MarshalToSizedBuffer(dAtA []byte) (int, error) copy(dAtA[i:], m.Proposer) i = encodeVarintEvents(dAtA, i, uint64(len(m.Proposer))) i-- - dAtA[i] = 0x32 - } - if len(m.Operation) > 0 { - i -= len(m.Operation) - copy(dAtA[i:], m.Operation) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Operation))) - i-- - dAtA[i] = 0x2a - } - if len(m.Role) > 0 { - i -= len(m.Role) - copy(dAtA[i:], m.Role) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Role))) - i-- dAtA[i] = 0x22 } if len(m.ChangeId) > 0 { @@ -918,20 +870,6 @@ func (m *EventRoleChangeApplied) MarshalToSizedBuffer(dAtA []byte) (int, error) _ = i var l int _ = l - if len(m.Operation) > 0 { - i -= len(m.Operation) - copy(dAtA[i:], m.Operation) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Operation))) - i-- - dAtA[i] = 0x2a - } - if len(m.Role) > 0 { - i -= len(m.Role) - copy(dAtA[i:], m.Role) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Role))) - i-- - dAtA[i] = 0x22 - } if len(m.ChangeId) > 0 { i -= len(m.ChangeId) copy(dAtA[i:], m.ChangeId) @@ -1090,14 +1028,6 @@ func (m *EventRoleChangeProposed) Size() (n int) { if l > 0 { n += 1 + l + sovEvents(uint64(l)) } - l = len(m.Role) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - l = len(m.Operation) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } l = len(m.Proposer) if l > 0 { n += 1 + l + sovEvents(uint64(l)) @@ -1140,14 +1070,6 @@ func (m *EventRoleChangeApplied) Size() (n int) { if l > 0 { n += 1 + l + sovEvents(uint64(l)) } - l = len(m.Role) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - l = len(m.Operation) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } return n } @@ -1981,70 +1903,6 @@ func (m *EventRoleChangeProposed) Unmarshal(dAtA []byte) error { m.ChangeId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Role = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Operation = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Proposer", wireType) } @@ -2336,70 +2194,6 @@ func (m *EventRoleChangeApplied) Unmarshal(dAtA []byte) error { } m.ChangeId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Role = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Operation = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipEvents(dAtA[iNdEx:]) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index ecd30eb431..0a6032bce7 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -125,16 +125,19 @@ func (m MsgProposeRoleChange) ValidateBasic() error { errs = append(errs, NewErrCodeInvalidField("key", "%s", err)) } - if err := m.Role.Validate(); err != nil { - errs = append(errs, NewErrCodeInvalidField("role", "%s", err)) - } - - if m.Operation == RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED { - errs = append(errs, NewErrCodeInvalidField("operation", "operation cannot be unspecified")) - } - - if err := validateAddresses(m.Addresses); err != nil { - errs = append(errs, NewErrCodeInvalidField("addresses", "%s", err)) + if len(m.RoleUpdates) == 0 { + errs = append(errs, NewErrCodeInvalidField("role_updates", "at least one role update is required")) + } else { + for i, update := range m.RoleUpdates { + if update.Role == RegistryRole_REGISTRY_ROLE_UNSPECIFIED { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role cannot be unspecified", i)) + } + for _, addr := range update.Addresses { + if _, err := sdk.AccAddressFromBech32(addr); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: invalid address: %s", i, err)) + } + } + } } return errors.Join(errs...) diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 47b11fcc49..b82fa787f1 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -157,21 +157,21 @@ func TestMsgProposeRoleChange_ValidateBasic(t *testing.T) { validAddr := sdk.AccAddress("propose_signer_________").String() otherAddr := sdk.AccAddress("propose_target_________").String() validKey := &RegistryKey{AssetClassId: "aclass", NftId: "nft1"} - grant := RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT + validUpdate := RoleUpdate{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr}} tests := []struct { name string msg MsgProposeRoleChange exp string }{ - {name: "valid", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}}, - {name: "empty signer", msg: MsgProposeRoleChange{Signer: "", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid signer: empty address"}, - {name: "bad signer", msg: MsgProposeRoleChange{Signer: "bad", Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid signer: decoding bech32"}, - {name: "nil key", msg: MsgProposeRoleChange{Signer: validAddr, Key: nil, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid key: registry key cannot be nil"}, - {name: "invalid role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED, Operation: grant, Addresses: []string{otherAddr}}, exp: "invalid role: cannot be unspecified"}, - {name: "unspecified operation", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED, Addresses: []string{otherAddr}}, exp: "invalid operation: operation cannot be unspecified"}, - {name: "no addresses", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{}}, exp: "invalid addresses: addresses cannot be empty"}, - {name: "bad address", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Operation: grant, Addresses: []string{"bad"}}, exp: "invalid addresses: decoding bech32"}, + {name: "valid", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}}, + {name: "valid clear role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER}}}}, + {name: "empty signer", msg: MsgProposeRoleChange{Signer: "", Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signer: empty address"}, + {name: "bad signer", msg: MsgProposeRoleChange{Signer: "bad", Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid signer: decoding bech32"}, + {name: "nil key", msg: MsgProposeRoleChange{Signer: validAddr, Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: registry key cannot be nil"}, + {name: "no role_updates", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, + {name: "unspecified role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, + {name: "bad address in update", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } for _, tc := range tests { diff --git a/x/registry/types/pending.go b/x/registry/types/pending.go index 4445954ee7..5fcaf7cc65 100644 --- a/x/registry/types/pending.go +++ b/x/registry/types/pending.go @@ -8,30 +8,29 @@ import ( "strings" ) -// ShortString returns a compact human-readable form of the operation (e.g. "GRANT"). -func (o RoleChangeOperation) ShortString() string { - return strings.TrimPrefix(o.String(), "ROLE_CHANGE_OPERATION_") -} - // NewPendingRoleChangeID computes the deterministic identifier for a pending role change. // -// The id is a hash of the registry key, role, operation, and the (order-independent) set of -// target addresses. Two proposals describing the same change therefore collapse onto the same -// pending change, so their approvals accumulate together. -func NewPendingRoleChangeID(key *RegistryKey, role RegistryRole, op RoleChangeOperation, addresses []string) string { - sorted := slices.Clone(addresses) - slices.Sort(sorted) +// The id is a hash of the registry key and the (order-independent) set of role updates, each +// reduced to its role and the order-independent set of desired addresses. Two proposals describing +// the same batch of role updates therefore collapse onto the same pending change, so their +// approvals accumulate together. +func NewPendingRoleChangeID(key *RegistryKey, roleUpdates []RoleUpdate) string { + // Reduce each update to a canonical "role|sortedAddrs" line, then sort the lines so the id is + // independent of both address order within a role and role order across the batch. + lines := make([]string, len(roleUpdates)) + for i, update := range roleUpdates { + sorted := slices.Clone(update.Addresses) + slices.Sort(sorted) + lines[i] = strconv.Itoa(int(update.Role)) + "|" + strings.Join(sorted, ",") + } + slices.Sort(lines) var b strings.Builder b.WriteString(key.AssetClassId) b.WriteByte(0) b.WriteString(key.NftId) b.WriteByte(0) - b.WriteString(strconv.Itoa(int(role))) - b.WriteByte(0) - b.WriteString(strconv.Itoa(int(op))) - b.WriteByte(0) - b.WriteString(strings.Join(sorted, ",")) + b.WriteString(strings.Join(lines, "\n")) sum := sha256.Sum256([]byte(b.String())) return hex.EncodeToString(sum[:]) diff --git a/x/registry/types/registry.pb.go b/x/registry/types/registry.pb.go index d8ebb5ce65..9f1d39d846 100644 --- a/x/registry/types/registry.pb.go +++ b/x/registry/types/registry.pb.go @@ -99,38 +99,6 @@ func (RegistryRole) EnumDescriptor() ([]byte, []int) { return fileDescriptor_2fa7a0b4d34d0208, []int{0} } -// RoleChangeOperation identifies the kind of role change a pending change applies. -type RoleChangeOperation int32 - -const ( - // ROLE_CHANGE_OPERATION_UNSPECIFIED is the default, invalid operation. - RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED RoleChangeOperation = 0 - // ROLE_CHANGE_OPERATION_GRANT adds the target addresses to the role when applied. - RoleChangeOperation_ROLE_CHANGE_OPERATION_GRANT RoleChangeOperation = 1 - // ROLE_CHANGE_OPERATION_REVOKE removes the target addresses from the role when applied. - RoleChangeOperation_ROLE_CHANGE_OPERATION_REVOKE RoleChangeOperation = 2 -) - -var RoleChangeOperation_name = map[int32]string{ - 0: "ROLE_CHANGE_OPERATION_UNSPECIFIED", - 1: "ROLE_CHANGE_OPERATION_GRANT", - 2: "ROLE_CHANGE_OPERATION_REVOKE", -} - -var RoleChangeOperation_value = map[string]int32{ - "ROLE_CHANGE_OPERATION_UNSPECIFIED": 0, - "ROLE_CHANGE_OPERATION_GRANT": 1, - "ROLE_CHANGE_OPERATION_REVOKE": 2, -} - -func (x RoleChangeOperation) String() string { - return proto.EnumName(RoleChangeOperation_name, int32(x)) -} - -func (RoleChangeOperation) EnumDescriptor() ([]byte, []int) { - return fileDescriptor_2fa7a0b4d34d0208, []int{1} -} - // RegistryKey represents a unique identifier for registry entries. // It links registry entries to specific NFT assets and their associated asset classes. type RegistryKey struct { @@ -306,32 +274,86 @@ func (m *RolesEntry) GetAddresses() []string { return nil } -// PendingRoleChange is a role change awaiting the approvals required by the role's authorization -// policy. Each required party submits a single-signer approval; once the accumulated approvers -// satisfy the policy, the change is applied and this record is removed. +// RoleUpdate specifies the desired set of addresses for a single role. +type RoleUpdate struct { + // role is the role to update. + Role RegistryRole `protobuf:"varint,1,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` + // addresses is the desired set of addresses for this role. + // An empty list means the role should be cleared entirely. + Addresses []string `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"` +} + +func (m *RoleUpdate) Reset() { *m = RoleUpdate{} } +func (m *RoleUpdate) String() string { return proto.CompactTextString(m) } +func (*RoleUpdate) ProtoMessage() {} +func (*RoleUpdate) Descriptor() ([]byte, []int) { + return fileDescriptor_2fa7a0b4d34d0208, []int{3} +} +func (m *RoleUpdate) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RoleUpdate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RoleUpdate.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RoleUpdate) XXX_Merge(src proto.Message) { + xxx_messageInfo_RoleUpdate.Merge(m, src) +} +func (m *RoleUpdate) XXX_Size() int { + return m.Size() +} +func (m *RoleUpdate) XXX_DiscardUnknown() { + xxx_messageInfo_RoleUpdate.DiscardUnknown(m) +} + +var xxx_messageInfo_RoleUpdate proto.InternalMessageInfo + +func (m *RoleUpdate) GetRole() RegistryRole { + if m != nil { + return m.Role + } + return RegistryRole_REGISTRY_ROLE_UNSPECIFIED +} + +func (m *RoleUpdate) GetAddresses() []string { + if m != nil { + return m.Addresses + } + return nil +} + +// PendingRoleChange is a role change awaiting the approvals required by the affected roles' +// authorization policies. Each required party submits a single-signer approval; once the +// accumulated approvers satisfy every affected role's policy, the change is applied atomically +// and this record is removed. type PendingRoleChange struct { // id is the deterministic identifier of this pending change - // (a hash of key + role + operation + sorted addresses). + // (a hash of key + the sorted set of role updates). Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // key identifies the registry entry the change applies to. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // role is the role being changed. - Role RegistryRole `protobuf:"varint,3,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` - // operation is the kind of change to apply (grant or revoke) once approved. - Operation RoleChangeOperation `protobuf:"varint,4,opt,name=operation,proto3,enum=provenance.registry.v1.RoleChangeOperation" json:"operation,omitempty"` - // addresses is the set of addresses the operation applies to. - Addresses []string `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` + // role_updates is the desired state for each role the change will set when applied. The same + // desired-state, multi-role semantics as MsgSetRoles, collected through accumulated approvals. + RoleUpdates []RoleUpdate `protobuf:"bytes,3,rep,name=role_updates,json=roleUpdates,proto3" json:"role_updates"` // proposer is the address that opened the pending change. - Proposer string `protobuf:"bytes,6,opt,name=proposer,proto3" json:"proposer,omitempty"` + Proposer string `protobuf:"bytes,4,opt,name=proposer,proto3" json:"proposer,omitempty"` // approvals is the accumulated set of addresses that have approved this change. - Approvals []string `protobuf:"bytes,7,rep,name=approvals,proto3" json:"approvals,omitempty"` + Approvals []string `protobuf:"bytes,5,rep,name=approvals,proto3" json:"approvals,omitempty"` } func (m *PendingRoleChange) Reset() { *m = PendingRoleChange{} } func (m *PendingRoleChange) String() string { return proto.CompactTextString(m) } func (*PendingRoleChange) ProtoMessage() {} func (*PendingRoleChange) Descriptor() ([]byte, []int) { - return fileDescriptor_2fa7a0b4d34d0208, []int{3} + return fileDescriptor_2fa7a0b4d34d0208, []int{4} } func (m *PendingRoleChange) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -374,23 +396,9 @@ func (m *PendingRoleChange) GetKey() *RegistryKey { return nil } -func (m *PendingRoleChange) GetRole() RegistryRole { - if m != nil { - return m.Role - } - return RegistryRole_REGISTRY_ROLE_UNSPECIFIED -} - -func (m *PendingRoleChange) GetOperation() RoleChangeOperation { +func (m *PendingRoleChange) GetRoleUpdates() []RoleUpdate { if m != nil { - return m.Operation - } - return RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED -} - -func (m *PendingRoleChange) GetAddresses() []string { - if m != nil { - return m.Addresses + return m.RoleUpdates } return nil } @@ -411,10 +419,10 @@ func (m *PendingRoleChange) GetApprovals() []string { func init() { proto.RegisterEnum("provenance.registry.v1.RegistryRole", RegistryRole_name, RegistryRole_value) - proto.RegisterEnum("provenance.registry.v1.RoleChangeOperation", RoleChangeOperation_name, RoleChangeOperation_value) proto.RegisterType((*RegistryKey)(nil), "provenance.registry.v1.RegistryKey") proto.RegisterType((*RegistryEntry)(nil), "provenance.registry.v1.RegistryEntry") proto.RegisterType((*RolesEntry)(nil), "provenance.registry.v1.RolesEntry") + proto.RegisterType((*RoleUpdate)(nil), "provenance.registry.v1.RoleUpdate") proto.RegisterType((*PendingRoleChange)(nil), "provenance.registry.v1.PendingRoleChange") } @@ -423,50 +431,47 @@ func init() { } var fileDescriptor_2fa7a0b4d34d0208 = []byte{ - // 678 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x94, 0x4f, 0x4f, 0x13, 0x41, - 0x18, 0xc6, 0xbb, 0xdb, 0x3f, 0xd0, 0x17, 0x24, 0xeb, 0x08, 0xd8, 0x02, 0x16, 0xac, 0x90, 0x20, - 0x86, 0x36, 0x20, 0x1a, 0xe3, 0xc1, 0xa4, 0x7f, 0x86, 0xba, 0xa1, 0xd9, 0x69, 0xa6, 0x5b, 0x08, - 0x5e, 0x36, 0xa5, 0x3b, 0x94, 0x0d, 0x65, 0x67, 0xb3, 0xbb, 0x12, 0x7b, 0xd1, 0x93, 0x9e, 0x3d, - 0x7a, 0xf4, 0x43, 0xf8, 0x21, 0x38, 0x12, 0x4f, 0x9e, 0x8c, 0x81, 0xa3, 0x5f, 0xc2, 0xec, 0x2e, - 0x6d, 0x29, 0x2d, 0x20, 0xb7, 0xdd, 0xf7, 0xf9, 0x3d, 0xf3, 0xbe, 0xef, 0x93, 0xc9, 0xc0, 0x92, - 0x65, 0xf3, 0x63, 0x66, 0xd6, 0xcd, 0x06, 0xcb, 0xda, 0xac, 0x69, 0x38, 0xae, 0xdd, 0xce, 0x1e, - 0xaf, 0x75, 0xbf, 0x33, 0x96, 0xcd, 0x5d, 0x8e, 0xa6, 0x7b, 0x58, 0xa6, 0x2b, 0x1d, 0xaf, 0xcd, - 0x4c, 0x36, 0x79, 0x93, 0xfb, 0x48, 0xd6, 0xfb, 0x0a, 0xe8, 0x99, 0x64, 0x83, 0x3b, 0x47, 0xdc, - 0xd1, 0x02, 0x21, 0xf8, 0x09, 0xa4, 0x74, 0x05, 0xc6, 0xe8, 0x85, 0x7f, 0x8b, 0xb5, 0xd1, 0x14, - 0xc4, 0xcc, 0x7d, 0x57, 0x33, 0xf4, 0x84, 0xb0, 0x20, 0x2c, 0xc7, 0x69, 0xd4, 0xdc, 0x77, 0x65, - 0x1d, 0x2d, 0xc2, 0x44, 0xdd, 0x71, 0x98, 0xab, 0x35, 0x5a, 0x75, 0xc7, 0xf1, 0x64, 0xd1, 0x97, - 0xc7, 0xfd, 0x6a, 0xc1, 0x2b, 0xca, 0xfa, 0xeb, 0xc8, 0xb7, 0xef, 0xf3, 0xa1, 0xf4, 0x17, 0x01, - 0xee, 0x75, 0x8e, 0xc4, 0xa6, 0x6b, 0xb7, 0xd1, 0x0b, 0x08, 0x1f, 0xb2, 0xb6, 0x7f, 0xe2, 0xd8, - 0xfa, 0x93, 0xcc, 0xf0, 0xd1, 0x33, 0x97, 0xc6, 0xa0, 0x1e, 0x8f, 0xde, 0x40, 0xd4, 0xe6, 0x2d, - 0xe6, 0x24, 0xc4, 0x85, 0xf0, 0xf2, 0xd8, 0x7a, 0xfa, 0x5a, 0xa3, 0x07, 0xf9, 0x9d, 0xf2, 0x91, - 0x93, 0xdf, 0xf3, 0x21, 0x1a, 0xd8, 0xd2, 0x1f, 0x01, 0x7a, 0x12, 0x7a, 0x05, 0x11, 0xaf, 0xec, - 0x4f, 0x31, 0xb1, 0xbe, 0x78, 0xdb, 0x14, 0x9e, 0x93, 0xfa, 0x0e, 0xf4, 0x12, 0xe2, 0x75, 0x5d, - 0xb7, 0x99, 0xe3, 0x5c, 0xcc, 0x12, 0xcf, 0x27, 0x7e, 0xfe, 0x58, 0x9d, 0xbc, 0xc8, 0x31, 0x17, - 0x68, 0x55, 0xd7, 0x36, 0xcc, 0x26, 0xed, 0xa1, 0xe9, 0xcf, 0x61, 0xb8, 0x5f, 0x61, 0xa6, 0xee, - 0x95, 0x79, 0x8b, 0x15, 0x0e, 0xea, 0x66, 0x93, 0xa1, 0x09, 0x10, 0xbb, 0xe9, 0x8a, 0x86, 0xde, - 0x09, 0x47, 0xbc, 0x63, 0x38, 0x9d, 0x75, 0xc2, 0x77, 0x5e, 0x47, 0x86, 0x38, 0xb7, 0x98, 0x5d, - 0x77, 0x0d, 0x6e, 0x26, 0x22, 0xbe, 0xfd, 0xd9, 0x4d, 0xd1, 0x06, 0x73, 0x93, 0x8e, 0x85, 0xf6, - 0xdc, 0xfd, 0xc9, 0x44, 0xff, 0x3b, 0x19, 0xb4, 0x01, 0xa3, 0x96, 0xcd, 0x2d, 0xee, 0x30, 0x3b, - 0x11, 0xf3, 0x92, 0xb8, 0xc1, 0xd6, 0x25, 0xfd, 0x6e, 0x96, 0x37, 0x68, 0xbd, 0xe5, 0x24, 0x46, - 0x6e, 0xed, 0xd6, 0x41, 0x57, 0xfe, 0x8a, 0x30, 0x7e, 0x39, 0x07, 0xf4, 0x08, 0x92, 0x14, 0x97, - 0xe4, 0xaa, 0x4a, 0x77, 0x35, 0x4a, 0xca, 0x58, 0xab, 0x29, 0xd5, 0x0a, 0x2e, 0xc8, 0x9b, 0x32, - 0x2e, 0x4a, 0x21, 0x34, 0x03, 0xd3, 0xfd, 0x72, 0x15, 0xd3, 0x6d, 0xb9, 0x80, 0xa9, 0x24, 0x0c, - 0x5a, 0xab, 0xb5, 0x7c, 0x57, 0x16, 0xd1, 0x1c, 0x24, 0xfa, 0xe5, 0x02, 0x51, 0x54, 0x4a, 0xca, - 0x65, 0x4c, 0xa5, 0x30, 0x9a, 0x85, 0x87, 0x57, 0xd4, 0x5a, 0x55, 0x25, 0x45, 0x39, 0xa7, 0x48, - 0x91, 0xc1, 0xae, 0x79, 0x42, 0x29, 0xd9, 0xc1, 0x54, 0x8a, 0x0e, 0x1e, 0x4b, 0xa8, 0x5c, 0x92, - 0x95, 0x9c, 0x4a, 0xa8, 0x14, 0x1b, 0x54, 0xcb, 0x32, 0x56, 0x34, 0xb2, 0xa3, 0x60, 0x2a, 0x8d, - 0xa0, 0x65, 0x58, 0xbc, 0xba, 0x4d, 0xa1, 0x46, 0x71, 0x51, 0xab, 0xe4, 0xa8, 0xba, 0xab, 0x6d, - 0x12, 0xea, 0xf3, 0xd2, 0x28, 0x7a, 0x0a, 0x4b, 0xb7, 0x91, 0x58, 0x21, 0x2a, 0x96, 0xe2, 0x28, - 0x09, 0x53, 0xfd, 0x68, 0xa5, 0x8c, 0x8b, 0x25, 0x8c, 0x25, 0x58, 0xf9, 0x04, 0x0f, 0x86, 0xdc, - 0x1a, 0xb4, 0x04, 0x8f, 0x83, 0x95, 0xdf, 0xe6, 0x94, 0x12, 0xd6, 0x48, 0x05, 0xd3, 0x9c, 0x2a, - 0x13, 0xe5, 0x4a, 0xf6, 0xf3, 0x30, 0x3b, 0x1c, 0x2b, 0xd1, 0x9c, 0xa2, 0x4a, 0x02, 0x5a, 0x80, - 0xb9, 0xe1, 0x00, 0xc5, 0xdb, 0x64, 0x0b, 0x4b, 0x62, 0xfe, 0xf0, 0xe4, 0x2c, 0x25, 0x9c, 0x9e, - 0xa5, 0x84, 0x3f, 0x67, 0x29, 0xe1, 0xeb, 0x79, 0x2a, 0x74, 0x7a, 0x9e, 0x0a, 0xfd, 0x3a, 0x4f, - 0x85, 0x20, 0x69, 0xf0, 0x6b, 0x2e, 0x7a, 0x45, 0x78, 0xb7, 0xd1, 0x34, 0xdc, 0x83, 0xf7, 0x7b, - 0x99, 0x06, 0x3f, 0xca, 0xf6, 0xa0, 0x55, 0x83, 0x5f, 0xfa, 0xcb, 0x7e, 0xe8, 0xbd, 0xc9, 0x6e, - 0xdb, 0x62, 0xce, 0x5e, 0xcc, 0x7f, 0x45, 0x9f, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x97, 0xb8, - 0x7b, 0x80, 0xb7, 0x05, 0x00, 0x00, + // 629 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x63, 0x27, 0x29, 0xcd, 0xa4, 0x54, 0x66, 0xd5, 0x16, 0xa7, 0x40, 0x5a, 0x85, 0x56, + 0x2a, 0x48, 0x4d, 0xd4, 0x52, 0x10, 0xe2, 0x80, 0x94, 0x3f, 0xdb, 0xca, 0x6a, 0x64, 0x47, 0x9b, + 0x84, 0xaa, 0x5c, 0x2c, 0x37, 0xde, 0xba, 0x56, 0x53, 0xaf, 0xe5, 0x75, 0x2b, 0x72, 0xe1, 0xc8, + 0x99, 0x0b, 0x12, 0x47, 0x1e, 0x82, 0x87, 0xe8, 0xb1, 0xe2, 0xc4, 0x09, 0xa1, 0xf6, 0xc8, 0x4b, + 0x20, 0xdb, 0x89, 0xd3, 0x34, 0x94, 0x88, 0x13, 0x37, 0xef, 0x7c, 0xbf, 0xd9, 0xf9, 0x66, 0xc6, + 0x5a, 0x58, 0x75, 0x3d, 0x76, 0x46, 0x1d, 0xc3, 0xe9, 0xd0, 0x92, 0x47, 0x2d, 0x9b, 0xfb, 0x5e, + 0xaf, 0x74, 0xb6, 0x11, 0x7f, 0x17, 0x5d, 0x8f, 0xf9, 0x0c, 0x2d, 0x0c, 0xb1, 0x62, 0x2c, 0x9d, + 0x6d, 0x2c, 0xce, 0x59, 0xcc, 0x62, 0x21, 0x52, 0x0a, 0xbe, 0x22, 0x7a, 0x31, 0xd7, 0x61, 0xfc, + 0x84, 0x71, 0x3d, 0x12, 0xa2, 0x43, 0x24, 0x15, 0x1a, 0x90, 0x25, 0xfd, 0xfc, 0x5d, 0xda, 0x43, + 0xf3, 0x30, 0xe5, 0x1c, 0xfa, 0xba, 0x6d, 0xca, 0xc2, 0xb2, 0xb0, 0x96, 0x21, 0x69, 0xe7, 0xd0, + 0x57, 0x4c, 0xb4, 0x02, 0xb3, 0x06, 0xe7, 0xd4, 0xd7, 0x3b, 0x5d, 0x83, 0xf3, 0x40, 0x16, 0x43, + 0x79, 0x26, 0x8c, 0x56, 0x83, 0xa0, 0x62, 0xbe, 0x4a, 0x7d, 0xfe, 0xb2, 0x94, 0x28, 0x7c, 0x10, + 0xe0, 0xee, 0xe0, 0x4a, 0xec, 0xf8, 0x5e, 0x0f, 0x3d, 0x87, 0xe4, 0x31, 0xed, 0x85, 0x37, 0x66, + 0x37, 0x1f, 0x17, 0xff, 0x6c, 0xbd, 0x78, 0xcd, 0x06, 0x09, 0x78, 0xf4, 0x1a, 0xd2, 0x1e, 0xeb, + 0x52, 0x2e, 0x8b, 0xcb, 0xc9, 0xb5, 0xec, 0x66, 0xe1, 0xd6, 0xc4, 0x00, 0x0a, 0x2b, 0x55, 0x52, + 0xe7, 0x3f, 0x96, 0x12, 0x24, 0x4a, 0x2b, 0xbc, 0x07, 0x18, 0x4a, 0xe8, 0x25, 0xa4, 0x82, 0x70, + 0xe8, 0x62, 0x76, 0x73, 0x65, 0x92, 0x8b, 0x20, 0x93, 0x84, 0x19, 0xe8, 0x05, 0x64, 0x0c, 0xd3, + 0xf4, 0x28, 0xe7, 0x7d, 0x2f, 0x99, 0x8a, 0xfc, 0xed, 0xeb, 0xfa, 0x5c, 0x7f, 0x8e, 0xe5, 0x48, + 0x6b, 0xfa, 0x9e, 0xed, 0x58, 0x64, 0x88, 0x0e, 0xea, 0xb7, 0x5d, 0xd3, 0xf0, 0xe9, 0x7f, 0xa8, + 0xff, 0x49, 0x84, 0x7b, 0x0d, 0xea, 0x98, 0x41, 0x98, 0x75, 0x69, 0xf5, 0xc8, 0x70, 0x2c, 0x8a, + 0x66, 0x41, 0x8c, 0xb7, 0x2b, 0xda, 0xe6, 0x60, 0x39, 0xe2, 0x3f, 0x2e, 0x67, 0x17, 0x66, 0x02, + 0x73, 0xfa, 0x69, 0xd8, 0x1d, 0x97, 0x93, 0x93, 0x77, 0x14, 0x0d, 0xa2, 0xbf, 0xa3, 0xac, 0x17, + 0x47, 0x38, 0xda, 0x82, 0x69, 0xd7, 0x63, 0x2e, 0xe3, 0xd4, 0x93, 0x53, 0x81, 0xb3, 0xbf, 0x34, + 0x18, 0x93, 0xe1, 0x5c, 0xdc, 0xa0, 0x9e, 0xd1, 0xe5, 0x72, 0x7a, 0xe2, 0x5c, 0x06, 0xe8, 0xd3, + 0x5f, 0x22, 0xcc, 0x5c, 0x1f, 0x33, 0x7a, 0x04, 0x39, 0x82, 0x77, 0x94, 0x66, 0x8b, 0xec, 0xeb, + 0x44, 0xab, 0x63, 0xbd, 0xad, 0x36, 0x1b, 0xb8, 0xaa, 0x6c, 0x2b, 0xb8, 0x26, 0x25, 0xd0, 0x22, + 0x2c, 0x8c, 0xca, 0x4d, 0x4c, 0xde, 0x28, 0x55, 0x4c, 0x24, 0x61, 0x3c, 0xb5, 0xd9, 0xae, 0xc4, + 0xb2, 0x88, 0x1e, 0x82, 0x3c, 0x2a, 0x57, 0x35, 0xb5, 0x45, 0xb4, 0x7a, 0x1d, 0x13, 0x29, 0x89, + 0x1e, 0xc0, 0xfd, 0x1b, 0x6a, 0xbb, 0xd9, 0xd2, 0x6a, 0x4a, 0x59, 0x95, 0x52, 0xe3, 0x55, 0x2b, + 0x1a, 0x21, 0xda, 0x1e, 0x26, 0x52, 0x7a, 0xfc, 0x5a, 0x8d, 0x28, 0x3b, 0x8a, 0x5a, 0x6e, 0x69, + 0x44, 0x9a, 0x1a, 0x57, 0xeb, 0x0a, 0x56, 0x75, 0x6d, 0x4f, 0xc5, 0x44, 0xba, 0x83, 0xd6, 0x60, + 0xe5, 0x66, 0x37, 0xd5, 0x36, 0xc1, 0x35, 0xbd, 0x51, 0x26, 0xad, 0x7d, 0x7d, 0x5b, 0x23, 0x21, + 0x2f, 0x4d, 0xa3, 0x27, 0xb0, 0x3a, 0x89, 0xc4, 0xaa, 0xd6, 0xc2, 0x52, 0x06, 0xe5, 0x60, 0x7e, + 0x14, 0x6d, 0xd4, 0x71, 0x6d, 0x07, 0x63, 0x09, 0x2a, 0xc7, 0xe7, 0x97, 0x79, 0xe1, 0xe2, 0x32, + 0x2f, 0xfc, 0xbc, 0xcc, 0x0b, 0x1f, 0xaf, 0xf2, 0x89, 0x8b, 0xab, 0x7c, 0xe2, 0xfb, 0x55, 0x3e, + 0x01, 0x39, 0x9b, 0xdd, 0xf2, 0xbb, 0x34, 0x84, 0xb7, 0x5b, 0x96, 0xed, 0x1f, 0x9d, 0x1e, 0x14, + 0x3b, 0xec, 0xa4, 0x34, 0x84, 0xd6, 0x6d, 0x76, 0xed, 0x54, 0x7a, 0x37, 0x7c, 0x22, 0xfd, 0x9e, + 0x4b, 0xf9, 0xc1, 0x54, 0xf8, 0xa8, 0x3d, 0xfb, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x01, 0x03, 0x85, + 0xeb, 0x46, 0x05, 0x00, 0x00, } func (m *RegistryKey) Marshal() (dAtA []byte, err error) { @@ -592,6 +597,43 @@ func (m *RolesEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *RoleUpdate) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleUpdate) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Addresses) > 0 { + for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addresses[iNdEx]) + copy(dAtA[i:], m.Addresses[iNdEx]) + i = encodeVarintRegistry(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Role != 0 { + i = encodeVarintRegistry(dAtA, i, uint64(m.Role)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *PendingRoleChange) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -618,7 +660,7 @@ func (m *PendingRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.Approvals[iNdEx]) i = encodeVarintRegistry(dAtA, i, uint64(len(m.Approvals[iNdEx]))) i-- - dAtA[i] = 0x3a + dAtA[i] = 0x2a } } if len(m.Proposer) > 0 { @@ -626,27 +668,22 @@ func (m *PendingRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { copy(dAtA[i:], m.Proposer) i = encodeVarintRegistry(dAtA, i, uint64(len(m.Proposer))) i-- - dAtA[i] = 0x32 + dAtA[i] = 0x22 } - if len(m.Addresses) > 0 { - for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Addresses[iNdEx]) - copy(dAtA[i:], m.Addresses[iNdEx]) - i = encodeVarintRegistry(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + if len(m.RoleUpdates) > 0 { + for iNdEx := len(m.RoleUpdates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleUpdates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintRegistry(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x1a } } - if m.Operation != 0 { - i = encodeVarintRegistry(dAtA, i, uint64(m.Operation)) - i-- - dAtA[i] = 0x20 - } - if m.Role != 0 { - i = encodeVarintRegistry(dAtA, i, uint64(m.Role)) - i-- - dAtA[i] = 0x18 - } if m.Key != nil { { size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) @@ -734,6 +771,24 @@ func (m *RolesEntry) Size() (n int) { return n } +func (m *RoleUpdate) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Role != 0 { + n += 1 + sovRegistry(uint64(m.Role)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { + l = len(s) + n += 1 + l + sovRegistry(uint64(l)) + } + } + return n +} + func (m *PendingRoleChange) Size() (n int) { if m == nil { return 0 @@ -748,15 +803,9 @@ func (m *PendingRoleChange) Size() (n int) { l = m.Key.Size() n += 1 + l + sovRegistry(uint64(l)) } - if m.Role != 0 { - n += 1 + sovRegistry(uint64(m.Role)) - } - if m.Operation != 0 { - n += 1 + sovRegistry(uint64(m.Operation)) - } - if len(m.Addresses) > 0 { - for _, s := range m.Addresses { - l = len(s) + if len(m.RoleUpdates) > 0 { + for _, e := range m.RoleUpdates { + l = e.Size() n += 1 + l + sovRegistry(uint64(l)) } } @@ -1114,6 +1163,107 @@ func (m *RolesEntry) Unmarshal(dAtA []byte) error { } return nil } +func (m *RoleUpdate) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoleUpdate: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoleUpdate: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + m.Role = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Role |= RegistryRole(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRegistry(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthRegistry + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *PendingRoleChange) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -1212,48 +1362,10 @@ func (m *PendingRoleChange) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) - } - m.Role = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRegistry - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Role |= RegistryRole(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) - } - m.Operation = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRegistry - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Operation |= RoleChangeOperation(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RoleUpdates", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRegistry @@ -1263,25 +1375,27 @@ func (m *PendingRoleChange) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthRegistry } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthRegistry } if postIndex > l { return io.ErrUnexpectedEOF } - m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + m.RoleUpdates = append(m.RoleUpdates, RoleUpdate{}) + if err := m.RoleUpdates[len(m.RoleUpdates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 6: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Proposer", wireType) } @@ -1313,7 +1427,7 @@ func (m *PendingRoleChange) Unmarshal(dAtA []byte) error { } m.Proposer = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 7: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Approvals", wireType) } diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 582b24c7b3..852dba5833 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -632,62 +632,6 @@ func (m *MsgSetRoles) GetRoleUpdates() []RoleUpdate { return nil } -// RoleUpdate specifies the desired set of addresses for a single role. -type RoleUpdate struct { - // role is the role to update. - Role RegistryRole `protobuf:"varint,1,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` - // addresses is the desired set of addresses for this role. - // An empty list means the role should be cleared entirely. - Addresses []string `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"` -} - -func (m *RoleUpdate) Reset() { *m = RoleUpdate{} } -func (m *RoleUpdate) String() string { return proto.CompactTextString(m) } -func (*RoleUpdate) ProtoMessage() {} -func (*RoleUpdate) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{11} -} -func (m *RoleUpdate) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *RoleUpdate) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_RoleUpdate.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *RoleUpdate) XXX_Merge(src proto.Message) { - xxx_messageInfo_RoleUpdate.Merge(m, src) -} -func (m *RoleUpdate) XXX_Size() int { - return m.Size() -} -func (m *RoleUpdate) XXX_DiscardUnknown() { - xxx_messageInfo_RoleUpdate.DiscardUnknown(m) -} - -var xxx_messageInfo_RoleUpdate proto.InternalMessageInfo - -func (m *RoleUpdate) GetRole() RegistryRole { - if m != nil { - return m.Role - } - return RegistryRole_REGISTRY_ROLE_UNSPECIFIED -} - -func (m *RoleUpdate) GetAddresses() []string { - if m != nil { - return m.Addresses - } - return nil -} - // MsgSetRolesResponse defines the response for SetRoles. type MsgSetRolesResponse struct { } @@ -696,7 +640,7 @@ func (m *MsgSetRolesResponse) Reset() { *m = MsgSetRolesResponse{} } func (m *MsgSetRolesResponse) String() string { return proto.CompactTextString(m) } func (*MsgSetRolesResponse) ProtoMessage() {} func (*MsgSetRolesResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{12} + return fileDescriptor_afab3f18b6d8353c, []int{11} } func (m *MsgSetRolesResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -726,27 +670,25 @@ func (m *MsgSetRolesResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSetRolesResponse proto.InternalMessageInfo // MsgProposeRoleChange opens a pending role change that collects single-signer approvals until -// the role's authorization policy is satisfied. Using single-signer messages (rather than one -// multi-signer message) keeps every step compatible with authz delegation. +// every affected role's authorization policy is satisfied, then applies the whole batch +// atomically. Its payload mirrors MsgSetRoles (desired-state, multi-role), but using single-signer +// messages (rather than one multi-signer message) keeps every step compatible with authz delegation. type MsgProposeRoleChange struct { // signer is the address proposing the change. It is recorded as the first approval; it only - // counts toward the policy if the signer is itself a required party. + // counts toward a policy if the signer is itself a required party. Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` // key identifies the registry entry to change. Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` - // role is the role being changed. - Role RegistryRole `protobuf:"varint,3,opt,name=role,proto3,enum=provenance.registry.v1.RegistryRole" json:"role,omitempty"` - // operation is the kind of change to apply once approved (grant or revoke). - Operation RoleChangeOperation `protobuf:"varint,4,opt,name=operation,proto3,enum=provenance.registry.v1.RoleChangeOperation" json:"operation,omitempty"` - // addresses is the set of addresses the operation applies to. - Addresses []string `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` + // role_updates contains the desired state for each role to set when the change is applied. + // At least one entry is required. Roles not listed here are unchanged. + RoleUpdates []RoleUpdate `protobuf:"bytes,3,rep,name=role_updates,json=roleUpdates,proto3" json:"role_updates"` } func (m *MsgProposeRoleChange) Reset() { *m = MsgProposeRoleChange{} } func (m *MsgProposeRoleChange) String() string { return proto.CompactTextString(m) } func (*MsgProposeRoleChange) ProtoMessage() {} func (*MsgProposeRoleChange) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{13} + return fileDescriptor_afab3f18b6d8353c, []int{12} } func (m *MsgProposeRoleChange) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -789,23 +731,9 @@ func (m *MsgProposeRoleChange) GetKey() *RegistryKey { return nil } -func (m *MsgProposeRoleChange) GetRole() RegistryRole { - if m != nil { - return m.Role - } - return RegistryRole_REGISTRY_ROLE_UNSPECIFIED -} - -func (m *MsgProposeRoleChange) GetOperation() RoleChangeOperation { - if m != nil { - return m.Operation - } - return RoleChangeOperation_ROLE_CHANGE_OPERATION_UNSPECIFIED -} - -func (m *MsgProposeRoleChange) GetAddresses() []string { +func (m *MsgProposeRoleChange) GetRoleUpdates() []RoleUpdate { if m != nil { - return m.Addresses + return m.RoleUpdates } return nil } @@ -823,7 +751,7 @@ func (m *MsgProposeRoleChangeResponse) Reset() { *m = MsgProposeRoleChan func (m *MsgProposeRoleChangeResponse) String() string { return proto.CompactTextString(m) } func (*MsgProposeRoleChangeResponse) ProtoMessage() {} func (*MsgProposeRoleChangeResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{14} + return fileDescriptor_afab3f18b6d8353c, []int{13} } func (m *MsgProposeRoleChangeResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -878,7 +806,7 @@ func (m *MsgApproveRoleChange) Reset() { *m = MsgApproveRoleChange{} } func (m *MsgApproveRoleChange) String() string { return proto.CompactTextString(m) } func (*MsgApproveRoleChange) ProtoMessage() {} func (*MsgApproveRoleChange) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{15} + return fileDescriptor_afab3f18b6d8353c, []int{14} } func (m *MsgApproveRoleChange) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -931,7 +859,7 @@ func (m *MsgApproveRoleChangeResponse) Reset() { *m = MsgApproveRoleChan func (m *MsgApproveRoleChangeResponse) String() string { return proto.CompactTextString(m) } func (*MsgApproveRoleChangeResponse) ProtoMessage() {} func (*MsgApproveRoleChangeResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{16} + return fileDescriptor_afab3f18b6d8353c, []int{15} } func (m *MsgApproveRoleChangeResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -979,7 +907,6 @@ func init() { proto.RegisterType((*MsgRegistryBulkUpdate)(nil), "provenance.registry.v1.MsgRegistryBulkUpdate") proto.RegisterType((*MsgRegistryBulkUpdateResponse)(nil), "provenance.registry.v1.MsgRegistryBulkUpdateResponse") proto.RegisterType((*MsgSetRoles)(nil), "provenance.registry.v1.MsgSetRoles") - proto.RegisterType((*RoleUpdate)(nil), "provenance.registry.v1.RoleUpdate") proto.RegisterType((*MsgSetRolesResponse)(nil), "provenance.registry.v1.MsgSetRolesResponse") proto.RegisterType((*MsgProposeRoleChange)(nil), "provenance.registry.v1.MsgProposeRoleChange") proto.RegisterType((*MsgProposeRoleChangeResponse)(nil), "provenance.registry.v1.MsgProposeRoleChangeResponse") @@ -990,59 +917,56 @@ func init() { func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 829 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0xcf, 0x4f, 0xe3, 0x46, - 0x14, 0xc7, 0x33, 0x49, 0xf8, 0x91, 0x17, 0x40, 0xad, 0xcb, 0x0f, 0xe3, 0xb6, 0x21, 0x0a, 0x50, - 0x45, 0xd0, 0x24, 0x90, 0x42, 0x85, 0x7a, 0xa8, 0x44, 0x2a, 0x5a, 0x21, 0x94, 0x16, 0x99, 0x72, - 0xa9, 0x2a, 0x45, 0x26, 0x19, 0x19, 0x2b, 0xc4, 0x63, 0xcd, 0x38, 0x29, 0xe1, 0x50, 0x55, 0x3d, - 0xf5, 0xd8, 0x7f, 0xa0, 0xff, 0x03, 0x87, 0xfe, 0x0d, 0x15, 0xbd, 0xa1, 0x3d, 0xed, 0x69, 0xb5, - 0x82, 0x03, 0xe7, 0xbd, 0xec, 0x5e, 0x57, 0x1e, 0x3b, 0x13, 0x9b, 0x24, 0x26, 0x80, 0xc4, 0x6a, - 0xb5, 0xb7, 0x8c, 0xfd, 0x79, 0xf3, 0xbe, 0xef, 0x3b, 0xf3, 0x66, 0x62, 0x58, 0xb0, 0x28, 0x69, - 0x61, 0x53, 0x33, 0xab, 0xb8, 0x40, 0xb1, 0x6e, 0x30, 0x9b, 0xb6, 0x0b, 0xad, 0xf5, 0x82, 0x7d, - 0x9a, 0xb7, 0x28, 0xb1, 0x89, 0x34, 0xdb, 0x05, 0xf2, 0x1d, 0x20, 0xdf, 0x5a, 0x57, 0xa6, 0x75, - 0xa2, 0x13, 0x8e, 0x14, 0x9c, 0x5f, 0x2e, 0xad, 0xcc, 0x57, 0x09, 0x6b, 0x10, 0x56, 0x71, 0x5f, - 0xb8, 0x03, 0xef, 0xd5, 0x9c, 0x3b, 0x2a, 0x34, 0x98, 0xee, 0x24, 0x68, 0x30, 0xdd, 0x7b, 0xb1, - 0x3c, 0x40, 0x82, 0xc8, 0xe6, 0x62, 0x2b, 0x03, 0x30, 0xad, 0x69, 0x1f, 0x13, 0x6a, 0x9c, 0x69, - 0xb6, 0x41, 0x4c, 0x97, 0xcd, 0xfc, 0x87, 0x60, 0xaa, 0xcc, 0x74, 0x95, 0x63, 0x98, 0xfe, 0xf8, - 0xfd, 0xcf, 0xd2, 0x1a, 0x8c, 0x32, 0x43, 0x37, 0x31, 0x95, 0x51, 0x1a, 0x65, 0x13, 0x25, 0xf9, - 0xd9, 0xbf, 0xb9, 0x69, 0x4f, 0xe0, 0x76, 0xad, 0x46, 0x31, 0x63, 0x07, 0x36, 0x35, 0x4c, 0x5d, - 0xf5, 0x38, 0x69, 0x13, 0x62, 0x75, 0xdc, 0x96, 0xa3, 0x69, 0x94, 0x4d, 0x16, 0x17, 0xf3, 0xfd, - 0x7d, 0xc8, 0xab, 0xde, 0xef, 0x3d, 0xdc, 0x56, 0x1d, 0x5e, 0xfa, 0x16, 0x46, 0x28, 0x39, 0xc1, - 0x4c, 0x8e, 0xa5, 0x63, 0xd9, 0x64, 0x31, 0x33, 0x30, 0xd0, 0x81, 0x76, 0x4c, 0x9b, 0xb6, 0x4b, - 0xf1, 0x8b, 0x17, 0x0b, 0x11, 0xd5, 0x0d, 0xfb, 0x26, 0xf9, 0xe7, 0xcd, 0xf9, 0x8a, 0xa7, 0x21, - 0x23, 0xc3, 0x6c, 0xb0, 0x0e, 0x15, 0x33, 0x8b, 0x98, 0x0c, 0x67, 0x5e, 0x23, 0x98, 0x28, 0x33, - 0xfd, 0x07, 0xaa, 0x99, 0xb6, 0x33, 0xd5, 0xd3, 0x15, 0xb8, 0x05, 0x71, 0x47, 0xa9, 0x1c, 0x4b, - 0xa3, 0xec, 0x54, 0x71, 0xe9, 0xae, 0x38, 0x47, 0x9c, 0xca, 0x23, 0xa4, 0xaf, 0x21, 0xa1, 0xb9, - 0x4a, 0x30, 0x93, 0xe3, 0xe9, 0x58, 0xa8, 0xca, 0x2e, 0x1a, 0xb4, 0x64, 0x16, 0xa6, 0xfd, 0x75, - 0x0b, 0x43, 0xde, 0x20, 0x98, 0xe4, 0x5e, 0xb5, 0x48, 0x1d, 0x7f, 0x50, 0x8e, 0xcc, 0xc1, 0x4c, - 0xa0, 0x70, 0x61, 0xc9, 0x5f, 0x08, 0x3e, 0x2a, 0x33, 0xfd, 0xd0, 0xa4, 0xef, 0xa0, 0x11, 0x82, - 0x1a, 0x15, 0x90, 0x6f, 0x2b, 0x11, 0x32, 0xff, 0x41, 0x5e, 0x01, 0xee, 0x04, 0xa5, 0xe6, 0x49, - 0xfd, 0xd0, 0xaa, 0x69, 0xf6, 0x43, 0x56, 0x70, 0x07, 0xc6, 0xb0, 0x69, 0x53, 0x03, 0x33, 0x39, - 0xca, 0xfb, 0x6f, 0xf9, 0x2e, 0xbd, 0xfe, 0x16, 0xec, 0xc4, 0x06, 0xb5, 0x2f, 0xc0, 0xe7, 0x7d, - 0xe5, 0x89, 0x02, 0x2e, 0x11, 0x24, 0xcb, 0x4c, 0x3f, 0xc0, 0x7c, 0x47, 0xb2, 0xa7, 0xdb, 0x78, - 0x7b, 0x30, 0xe1, 0x6c, 0xa3, 0x4a, 0x93, 0xeb, 0x19, 0xea, 0xc8, 0x71, 0xa5, 0x7b, 0xf5, 0x26, - 0xa9, 0x78, 0x72, 0xab, 0xe6, 0xdf, 0x01, 0xba, 0xb4, 0xd8, 0xe0, 0xe8, 0x71, 0x1b, 0x3c, 0x3a, - 0xf4, 0x06, 0xcf, 0xcc, 0xc0, 0x27, 0x3e, 0x47, 0x85, 0xd3, 0xff, 0x47, 0x79, 0xf7, 0xef, 0x53, - 0x62, 0x11, 0xc6, 0x37, 0xfb, 0x77, 0xc7, 0x9a, 0xa9, 0xbf, 0x17, 0xbd, 0xbe, 0x0b, 0x09, 0x62, - 0x61, 0xca, 0xef, 0x29, 0x39, 0xce, 0xc3, 0x57, 0xc3, 0x56, 0xca, 0xad, 0xec, 0xa7, 0x4e, 0x88, - 0xda, 0x8d, 0x0e, 0xba, 0x3a, 0xf2, 0xc0, 0x63, 0xe3, 0x10, 0x3e, 0xeb, 0x67, 0x65, 0xc7, 0x6b, - 0xe9, 0x53, 0x48, 0x54, 0xf9, 0x93, 0x8a, 0x51, 0x73, 0x5d, 0x55, 0xc7, 0xdd, 0x07, 0xbb, 0x35, - 0x49, 0x86, 0x31, 0xcd, 0xb2, 0x4e, 0x0c, 0x5c, 0xe3, 0x0e, 0x8e, 0xab, 0x9d, 0x61, 0x86, 0xf2, - 0x15, 0xda, 0xb6, 0x78, 0x65, 0x8f, 0x5a, 0xa1, 0x80, 0x80, 0x68, 0x50, 0x40, 0xb0, 0x94, 0x2d, - 0x5e, 0x4a, 0x4f, 0x4e, 0x51, 0x8a, 0x4f, 0x2d, 0x0a, 0xa8, 0x2d, 0xbe, 0x1a, 0x85, 0x58, 0x99, - 0xe9, 0x12, 0x86, 0xa4, 0xff, 0xdf, 0xc2, 0x17, 0x83, 0x16, 0x26, 0x78, 0x1b, 0x2b, 0xf9, 0xe1, - 0x38, 0x21, 0xa4, 0x02, 0x89, 0xee, 0x8d, 0xbd, 0x14, 0x12, 0x2c, 0x28, 0xe5, 0xcb, 0x61, 0x28, - 0x91, 0xe0, 0x08, 0xc0, 0x77, 0x03, 0x2e, 0x87, 0xca, 0xeb, 0x60, 0x4a, 0x6e, 0x28, 0x4c, 0xe4, - 0xa8, 0xc3, 0x64, 0xf0, 0x4a, 0xc9, 0x86, 0xc4, 0x07, 0x48, 0x65, 0x6d, 0x58, 0x52, 0x24, 0x3b, - 0x03, 0xa9, 0xcf, 0xc5, 0x90, 0xbb, 0xd3, 0x77, 0x3f, 0xae, 0x6c, 0xde, 0x0b, 0x17, 0xb9, 0x7f, - 0x85, 0x71, 0x71, 0xa6, 0x2f, 0x86, 0x4c, 0xd1, 0x81, 0x94, 0xd5, 0x21, 0x20, 0x31, 0xfb, 0x6f, - 0xf0, 0x71, 0xef, 0x39, 0x16, 0xb6, 0xda, 0x3d, 0xb4, 0xb2, 0x71, 0x1f, 0xda, 0x9f, 0xb8, 0xb7, - 0x3d, 0xc3, 0x12, 0xf7, 0xd0, 0xa1, 0x89, 0x07, 0xb6, 0xa1, 0x32, 0xf2, 0xc7, 0xcd, 0xf9, 0x0a, - 0x2a, 0xd5, 0x2f, 0xae, 0x52, 0xe8, 0xf2, 0x2a, 0x85, 0x5e, 0x5e, 0xa5, 0xd0, 0xdf, 0xd7, 0xa9, - 0xc8, 0xe5, 0x75, 0x2a, 0xf2, 0xfc, 0x3a, 0x15, 0x81, 0x79, 0x83, 0x0c, 0x98, 0x78, 0x1f, 0xfd, - 0xb2, 0xa1, 0x1b, 0xf6, 0x71, 0xf3, 0x28, 0x5f, 0x25, 0x8d, 0x42, 0x17, 0xca, 0x19, 0xc4, 0x37, - 0x2a, 0x9c, 0x76, 0xbf, 0x0d, 0xec, 0xb6, 0x85, 0xd9, 0xd1, 0x28, 0xff, 0x22, 0xf8, 0xea, 0x6d, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x08, 0x21, 0xe3, 0xd1, 0xe9, 0x0c, 0x00, 0x00, + // 781 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x96, 0xcf, 0x4f, 0xdb, 0x48, + 0x14, 0xc7, 0x33, 0x84, 0x5f, 0x79, 0x01, 0xb4, 0xeb, 0xe5, 0x87, 0xf1, 0xee, 0x86, 0x28, 0xc0, + 0x2a, 0x62, 0x37, 0x09, 0x64, 0xa1, 0x42, 0x3d, 0x54, 0x22, 0x15, 0xad, 0x2a, 0x94, 0x0a, 0x99, + 0x72, 0xa9, 0x2a, 0x45, 0x26, 0x19, 0x19, 0x2b, 0xc4, 0x63, 0xcd, 0x38, 0x29, 0xe1, 0x54, 0xf5, + 0xd4, 0x63, 0xff, 0x81, 0xfe, 0x0f, 0x1c, 0xfa, 0x37, 0x54, 0x1c, 0x51, 0x4f, 0xed, 0xa5, 0xaa, + 0xe0, 0xc0, 0xb9, 0x97, 0xf6, 0x5a, 0x79, 0xec, 0x4c, 0x6c, 0x92, 0x98, 0xd0, 0x4a, 0x54, 0x6a, + 0x6f, 0x1e, 0xcf, 0xe7, 0xcd, 0xfb, 0x7e, 0x9f, 0xe7, 0xcd, 0x18, 0xe6, 0x2c, 0x4a, 0x1a, 0xd8, + 0xd4, 0xcc, 0x32, 0xce, 0x51, 0xac, 0x1b, 0xcc, 0xa6, 0xcd, 0x5c, 0x63, 0x25, 0x67, 0x1f, 0x66, + 0x2d, 0x4a, 0x6c, 0x22, 0x4d, 0xb7, 0x81, 0x6c, 0x0b, 0xc8, 0x36, 0x56, 0x94, 0x49, 0x9d, 0xe8, + 0x84, 0x23, 0x39, 0xe7, 0xc9, 0xa5, 0x95, 0xd9, 0x32, 0x61, 0x35, 0xc2, 0x4a, 0xee, 0x84, 0x3b, + 0xf0, 0xa6, 0x66, 0xdc, 0x51, 0xae, 0xc6, 0x74, 0x27, 0x41, 0x8d, 0xe9, 0xde, 0xc4, 0x62, 0x0f, + 0x09, 0x22, 0x9b, 0x8b, 0x2d, 0xf5, 0xc0, 0xb4, 0xba, 0xbd, 0x4f, 0xa8, 0x71, 0xa4, 0xd9, 0x06, + 0x31, 0x5d, 0x36, 0xf5, 0x06, 0xc1, 0x44, 0x91, 0xe9, 0x2a, 0xc7, 0x30, 0x7d, 0x78, 0xef, 0x91, + 0xb4, 0x0c, 0xc3, 0xcc, 0xd0, 0x4d, 0x4c, 0x65, 0x94, 0x44, 0xe9, 0x58, 0x41, 0x7e, 0xfb, 0x3a, + 0x33, 0xe9, 0x09, 0xdc, 0xa8, 0x54, 0x28, 0x66, 0x6c, 0xc7, 0xa6, 0x86, 0xa9, 0xab, 0x1e, 0x27, + 0xad, 0x41, 0xb4, 0x8a, 0x9b, 0xf2, 0x40, 0x12, 0xa5, 0xe3, 0xf9, 0xf9, 0x6c, 0xf7, 0x3a, 0x64, + 0x55, 0xef, 0x79, 0x0b, 0x37, 0x55, 0x87, 0x97, 0xee, 0xc0, 0x10, 0x25, 0x07, 0x98, 0xc9, 0xd1, + 0x64, 0x34, 0x1d, 0xcf, 0xa7, 0x7a, 0x06, 0x3a, 0xd0, 0xa6, 0x69, 0xd3, 0x66, 0x61, 0xf0, 0xe4, + 0xc3, 0x5c, 0x44, 0x75, 0xc3, 0x6e, 0xc7, 0x9f, 0x5f, 0x1c, 0x2f, 0x79, 0x1a, 0x52, 0x32, 0x4c, + 0x07, 0x7d, 0xa8, 0x98, 0x59, 0xc4, 0x64, 0x38, 0xf5, 0x19, 0xc1, 0x58, 0x91, 0xe9, 0xf7, 0xa9, + 0x66, 0xda, 0xce, 0x52, 0x37, 0x67, 0x70, 0x1d, 0x06, 0x1d, 0xa5, 0x72, 0x34, 0x89, 0xd2, 0x13, + 0xf9, 0x85, 0xab, 0xe2, 0x1c, 0x71, 0x2a, 0x8f, 0x90, 0x6e, 0x41, 0x4c, 0x73, 0x95, 0x60, 0x26, + 0x0f, 0x26, 0xa3, 0xa1, 0x2a, 0xdb, 0x68, 0xb0, 0x24, 0xd3, 0x30, 0xe9, 0xf7, 0x2d, 0x0a, 0xf2, + 0x05, 0xc1, 0x38, 0xaf, 0x55, 0x83, 0x54, 0xf1, 0x2f, 0x55, 0x91, 0x19, 0x98, 0x0a, 0x18, 0x17, + 0x25, 0x79, 0x81, 0xe0, 0xb7, 0x22, 0xd3, 0x77, 0x4d, 0xfa, 0x03, 0x1a, 0x21, 0xa8, 0x51, 0x01, + 0xf9, 0xb2, 0x12, 0x21, 0xf3, 0x15, 0xf2, 0x0c, 0xb8, 0x0b, 0x14, 0xea, 0x07, 0xd5, 0x5d, 0xab, + 0xa2, 0xd9, 0xdf, 0xf2, 0x05, 0x37, 0x61, 0x04, 0x9b, 0x36, 0x35, 0x30, 0x93, 0x07, 0x78, 0xff, + 0x2d, 0x5e, 0xa5, 0xd7, 0xdf, 0x82, 0xad, 0xd8, 0xa0, 0xf6, 0x39, 0xf8, 0xbb, 0xab, 0x3c, 0x61, + 0xe0, 0x14, 0x41, 0xbc, 0xc8, 0xf4, 0x1d, 0xcc, 0x77, 0x24, 0xbb, 0xb9, 0x8d, 0xb7, 0x05, 0x63, + 0xce, 0x36, 0x2a, 0xd5, 0xb9, 0x9e, 0xbe, 0x8e, 0x1c, 0x57, 0xba, 0xe7, 0x37, 0x4e, 0xc5, 0x9b, + 0x4b, 0x9e, 0xa7, 0xe0, 0x0f, 0x9f, 0x23, 0xe1, 0xf4, 0x3d, 0xe2, 0xdd, 0xb7, 0x4d, 0x89, 0x45, + 0x18, 0xdf, 0x6c, 0x77, 0xf7, 0x35, 0x53, 0xc7, 0x3f, 0x83, 0xe5, 0x5d, 0xf8, 0xab, 0x9b, 0xb5, + 0x96, 0x77, 0xe9, 0x4f, 0x88, 0x95, 0xf9, 0x9b, 0x92, 0x51, 0x71, 0x5d, 0xaa, 0xa3, 0xee, 0x8b, + 0x07, 0x15, 0x49, 0x86, 0x11, 0xcd, 0xb2, 0x0e, 0x0c, 0x5c, 0xe1, 0x8e, 0x46, 0xd5, 0xd6, 0x30, + 0x45, 0x79, 0xc5, 0x36, 0x2c, 0x2e, 0xf0, 0xbb, 0x2a, 0x16, 0x10, 0x30, 0x10, 0x14, 0x10, 0xb4, + 0xb2, 0xce, 0xad, 0x74, 0xe4, 0x14, 0x56, 0x7c, 0x6a, 0x51, 0x40, 0x6d, 0xfe, 0xd3, 0x30, 0x44, + 0x8b, 0x4c, 0x97, 0x30, 0xc4, 0xfd, 0xb7, 0xe7, 0x3f, 0xbd, 0xea, 0x1b, 0xbc, 0x9d, 0x94, 0x6c, + 0x7f, 0x9c, 0x10, 0x52, 0x82, 0x58, 0xfb, 0x06, 0x5b, 0x08, 0x09, 0x16, 0x94, 0xf2, 0x5f, 0x3f, + 0x94, 0x48, 0xb0, 0x07, 0xe0, 0xbb, 0x11, 0x16, 0x43, 0xe5, 0xb5, 0x30, 0x25, 0xd3, 0x17, 0x26, + 0x72, 0x54, 0x61, 0x3c, 0x78, 0xc4, 0xa6, 0x43, 0xe2, 0x03, 0xa4, 0xb2, 0xdc, 0x2f, 0x29, 0x92, + 0x1d, 0x81, 0xd4, 0xe5, 0xa0, 0xcc, 0x5c, 0x59, 0x77, 0x3f, 0xae, 0xac, 0x5d, 0x0b, 0x17, 0xb9, + 0x9f, 0xc0, 0xa8, 0x38, 0xe3, 0xe6, 0x43, 0x96, 0x68, 0x41, 0xca, 0xbf, 0x7d, 0x40, 0x62, 0xf5, + 0xa7, 0xf0, 0x7b, 0xe7, 0xb9, 0x12, 0xf6, 0xb5, 0x3b, 0x68, 0x65, 0xf5, 0x3a, 0xb4, 0x3f, 0x71, + 0x67, 0x7b, 0x86, 0x25, 0xee, 0xa0, 0x43, 0x13, 0xf7, 0x6c, 0x43, 0x65, 0xe8, 0xd9, 0xc5, 0xf1, + 0x12, 0x2a, 0x54, 0x4f, 0xce, 0x12, 0xe8, 0xf4, 0x2c, 0x81, 0x3e, 0x9e, 0x25, 0xd0, 0xcb, 0xf3, + 0x44, 0xe4, 0xf4, 0x3c, 0x11, 0x79, 0x77, 0x9e, 0x88, 0xc0, 0xac, 0x41, 0x7a, 0x2c, 0xbc, 0x8d, + 0x1e, 0xaf, 0xea, 0x86, 0xbd, 0x5f, 0xdf, 0xcb, 0x96, 0x49, 0x2d, 0xd7, 0x86, 0x32, 0x06, 0xf1, + 0x8d, 0x72, 0x87, 0xed, 0x7f, 0x65, 0xbb, 0x69, 0x61, 0xb6, 0x37, 0xcc, 0xff, 0x90, 0xff, 0xff, + 0x1a, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x25, 0x93, 0x3c, 0xf9, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1847,43 +1771,6 @@ func (m *MsgSetRoles) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *RoleUpdate) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *RoleUpdate) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *RoleUpdate) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.Addresses) > 0 { - for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Addresses[iNdEx]) - copy(dAtA[i:], m.Addresses[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.Addresses[iNdEx]))) - i-- - dAtA[i] = 0x12 - } - } - if m.Role != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.Role)) - i-- - dAtA[i] = 0x8 - } - return len(dAtA) - i, nil -} - func (m *MsgSetRolesResponse) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1927,25 +1814,20 @@ func (m *MsgProposeRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.Addresses) > 0 { - for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Addresses[iNdEx]) - copy(dAtA[i:], m.Addresses[iNdEx]) - i = encodeVarintTx(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + if len(m.RoleUpdates) > 0 { + for iNdEx := len(m.RoleUpdates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleUpdates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } i-- - dAtA[i] = 0x2a + dAtA[i] = 0x1a } } - if m.Operation != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.Operation)) - i-- - dAtA[i] = 0x20 - } - if m.Role != 0 { - i = encodeVarintTx(dAtA, i, uint64(m.Role)) - i-- - dAtA[i] = 0x18 - } if m.Key != nil { { size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) @@ -2268,24 +2150,6 @@ func (m *MsgSetRoles) Size() (n int) { return n } -func (m *RoleUpdate) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if m.Role != 0 { - n += 1 + sovTx(uint64(m.Role)) - } - if len(m.Addresses) > 0 { - for _, s := range m.Addresses { - l = len(s) - n += 1 + l + sovTx(uint64(l)) - } - } - return n -} - func (m *MsgSetRolesResponse) Size() (n int) { if m == nil { return 0 @@ -2309,15 +2173,9 @@ func (m *MsgProposeRoleChange) Size() (n int) { l = m.Key.Size() n += 1 + l + sovTx(uint64(l)) } - if m.Role != 0 { - n += 1 + sovTx(uint64(m.Role)) - } - if m.Operation != 0 { - n += 1 + sovTx(uint64(m.Operation)) - } - if len(m.Addresses) > 0 { - for _, s := range m.Addresses { - l = len(s) + if len(m.RoleUpdates) > 0 { + for _, e := range m.RoleUpdates { + l = e.Size() n += 1 + l + sovTx(uint64(l)) } } @@ -3501,107 +3359,6 @@ func (m *MsgSetRoles) Unmarshal(dAtA []byte) error { } return nil } -func (m *RoleUpdate) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RoleUpdate: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RoleUpdate: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) - } - m.Role = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Role |= RegistryRole(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthTx - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthTx - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipTx(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthTx - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *MsgSetRolesResponse) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -3750,48 +3507,10 @@ func (m *MsgProposeRoleChange) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) - } - m.Role = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Role |= RegistryRole(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Operation", wireType) - } - m.Operation = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTx - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - m.Operation |= RoleChangeOperation(b&0x7F) << shift - if b < 0x80 { - break - } - } - case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RoleUpdates", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowTx @@ -3801,23 +3520,25 @@ func (m *MsgProposeRoleChange) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthTx } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLengthTx } if postIndex > l { return io.ErrUnexpectedEOF } - m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + m.RoleUpdates = append(m.RoleUpdates, RoleUpdate{}) + if err := m.RoleUpdates[len(m.RoleUpdates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex From 26e158104c720806f2ca0389f1f696ac21323ac6 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 09:46:13 -0600 Subject: [PATCH 08/36] Complete ticket test matrix: scenario 2 single-signer rejects and authz reject matrix Add the three single-signer reject subtests the ticket enumerates for the Controller + Secured Party scenario (only current controller, only new controller, only secured party). Extend authz coverage to the full ticket reject matrix via a neutral executor that carries each party's delegated authority, with the proposal and every approval dispatched through authz MsgExec. Add a scenario 1 (no secured party) authz happy path to mirror "same scenarios as above" via authz. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...ole_change_accumulation_acceptance_test.go | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index 3a483168f4..7cca8432c8 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "strings" "testing" "time" @@ -259,6 +260,37 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerSet_WithSecure s.Require().False(applied, "an unrelated approval does not satisfy the policy") s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) }) + + s.Run("incomplete: only current controller approves", func() { + key := s.mintNFT("acc-b-cc-only") + s.setupRegistry(key, baseRoles()) + + _, applied := s.proposeNewController(key, s.currentController) + s.Require().False(applied, "missing secured party and new controller approvals") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: only new controller approves", func() { + key := s.mintNFT("acc-b-nc-only") + s.setupRegistry(key, baseRoles()) + + _, applied := s.proposeNewController(key, s.newController) + s.Require().False(applied, "missing current controller and secured party approvals") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) + + s.Run("incomplete: only secured party approves", func() { + key := s.mintNFT("acc-b-sp-only") + s.setupRegistry(key, baseRoles()) + + changeID, applied := s.proposeNewController(key, s.securedParty) + s.Require().False(applied, "proposing as the secured party records only its own approval") + + // Re-stating the secured party's approval must remain insufficient. + applied = s.approve(changeID, s.securedParty) + s.Require().False(applied, "missing current controller and new controller approvals") + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + }) } // --- AuthZ delegation ----------------------------------------------------------------- @@ -290,6 +322,29 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) execApproveViaAuthz(executor s.Require().NoError(err, "authz must dispatch a single-signer approval") } +// grantProposeAuthz grants executor authority to submit MsgProposeRoleChange on behalf of granter. +func (s *RoleChangeAccumulationAcceptanceTestSuite) grantProposeAuthz(executor, granter string) { + auth := authz.NewGenericAuthorization(sdk.MsgTypeURL(&types.MsgProposeRoleChange{})) + s.Require().NoError(s.app.AuthzKeeper.SaveGrant(s.ctx, s.mustAddr(executor), s.mustAddr(granter), auth, nil)) +} + +// execProposeNewControllerViaAuthz submits MsgProposeRoleChange (signed by granter) to grant the +// CONTROLLER role to newController, wrapped in a MsgExec run by executor. Returns the change id. +func (s *RoleChangeAccumulationAcceptanceTestSuite) execProposeNewControllerViaAuthz(executor, granter string, key *types.RegistryKey) string { + inner := &types.MsgProposeRoleChange{ + Signer: granter, + Key: key, + RoleUpdates: []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: []string{s.newController}, + }}, + } + msgExec := authz.NewMsgExec(s.mustAddr(executor), []sdk.Msg{inner}) + _, err := s.app.AuthzKeeper.Exec(s.ctx, &msgExec) + s.Require().NoError(err, "authz must dispatch a single-signer proposal") + return types.NewPendingRoleChangeID(key, inner.RoleUpdates) +} + func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_Accumulation_ViaAuthz() { controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE @@ -323,6 +378,88 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_Accumul s.Require().Nil(pending, "pending change should be removed after it applies") } +// TestControllerUpdate_NoSecuredParty_HappyPath_ViaAuthz proves the Scenario 1 happy path (no +// Secured Party set) also completes when both required approvals are delegated through authz. +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_NoSecuredParty_HappyPath_ViaAuthz() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + executor := genAddr() + + key := s.mintNFT("acc-authz-no-sp") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + }) + + // Both the current controller (proposer) and the new controller delegate via authz. + s.grantProposeAuthz(executor, s.currentController) + s.grantApproveAuthz(executor, s.newController) + + changeID := s.execProposeNewControllerViaAuthz(executor, s.currentController, key) + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController, + "not yet applied after only the current controller's delegated proposal") + + s.execApproveViaAuthz(executor, s.newController, changeID) + s.Require().Contains(s.roleAddresses(key, controllerRole), s.newController, + "current + new controller approvals (both authz-delegated) satisfy the policy") + + pending, err := s.registryKeeper.GetPendingRoleChange(s.ctx, changeID) + s.Require().NoError(err) + s.Require().Nil(pending, "pending change should be removed after it applies") +} + +// TestControllerUpdate_RejectMatrix_ViaAuthz re-runs the ticket's reject scenarios with every +// approval (and the proposal) delegated through authz MsgExec, proving that an incomplete set of +// authz-delegated approvals never satisfies the policy. A neutral executor carries each party's +// delegated authority, so no required party ever signs a transaction directly. +func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_RejectMatrix_ViaAuthz() { + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedPartyRole := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + // authzApprovers opens the pending change as the first party (via authz) and submits the rest of + // the partial approver set (via authz), then asserts the change never applied. + authzApprovers := func(name string, withSecuredParty bool, approvers []string) { + s.Run(name, func() { + executor := genAddr() + key := s.mintNFT("acc-authz-reject-" + strings.ReplaceAll(name, "_", "-")) + + roles := []types.RolesEntry{{Role: controllerRole, Addresses: []string{s.currentController}}} + if withSecuredParty { + roles = append(roles, types.RolesEntry{Role: securedPartyRole, Addresses: []string{s.securedParty}}) + } + s.setupRegistry(key, roles) + + // Delegate every party's authority to the executor so nothing is signed directly. + for _, p := range approvers { + s.grantProposeAuthz(executor, p) + s.grantApproveAuthz(executor, p) + } + + // The first party opens the change via authz; the rest approve via authz. + changeID := s.execProposeNewControllerViaAuthz(executor, approvers[0], key) + for _, p := range approvers[1:] { + s.execApproveViaAuthz(executor, p, changeID) + } + + s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController, + "incomplete authz-delegated approval set must not satisfy the policy") + pending, err := s.registryKeeper.GetPendingRoleChange(s.ctx, changeID) + s.Require().NoError(err) + s.Require().NotNil(pending, "the pending change should remain open while approvals are incomplete") + }) + } + + // Scenario 1: Controller set, no Secured Party for eNote. + authzApprovers("no_sp_only_current_controller", false, []string{s.currentController}) + authzApprovers("no_sp_only_new_controller", false, []string{s.newController}) + + // Scenario 2: Controller and Secured Party for eNote set. + authzApprovers("sp_only_current_controller", true, []string{s.currentController}) + authzApprovers("sp_only_new_controller", true, []string{s.newController}) + authzApprovers("sp_only_secured_party", true, []string{s.securedParty}) + authzApprovers("sp_current_controller_and_secured_party", true, []string{s.currentController, s.securedParty}) + authzApprovers("sp_current_controller_and_new_controller", true, []string{s.currentController, s.newController}) + authzApprovers("sp_secured_party_and_new_controller", true, []string{s.securedParty, s.newController}) +} + // --- Scenario C: Controller revoke accumulation --------------------------------------- func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerRevoke_Accumulation() { From 8db7bbed2413ca5b41538cbe125b498b58f093d1 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 13:49:30 -0600 Subject: [PATCH 09/36] Address PR #1 review comments on registry authorization - Sort and de-duplicate addresses in NewPendingRoleChangeID so the id is truly set-based (c1) - Emit EventRoleChangeApproved only on newly recorded approvals (c2) - SetRoles returns RegistryNotFound only for collections.ErrNotFound; wrap other errors (c3) - Reject NftRole used with non-ASSIGNMENT_CURRENT* variants (c4) - Ignore approvals from addresses not eligible under the role policy to prevent state bloat/DoS (c5) - Deep-copy the original entry in SetRoles before mutation so diff events are correct (c6) - Validate role enum (Role.Validate()) per RoleUpdate in MsgSetRoles/MsgProposeRoleChange (c7) - Enforce exactly-one cardinality for ASSIGNMENT_CURRENT/ASSIGNMENT_NEW (c8) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/authorization.go | 126 ++++++++++++++---- x/registry/keeper/keeper.go | 13 +- x/registry/keeper/pending.go | 31 ++++- ...ole_change_accumulation_acceptance_test.go | 8 ++ x/registry/types/msgs.go | 8 +- x/registry/types/msgs_test.go | 2 + x/registry/types/pending.go | 1 + 7 files changed, 155 insertions(+), 34 deletions(-) diff --git a/x/registry/keeper/authorization.go b/x/registry/keeper/authorization.go index ec1c023ab3..b391c6c50b 100644 --- a/x/registry/keeper/authorization.go +++ b/x/registry/keeper/authorization.go @@ -8,79 +8,115 @@ import ( "github.com/provenance-io/provenance/x/registry/types" ) -// resolveRegistryRoleAddresses returns the addresses currently held for a given registry role -// (ASSIGNMENT_CURRENT variants) or the incoming new addresses (ASSIGNMENT_NEW variants). -func (k Keeper) resolveRegistryRoleAddresses(entry *types.RegistryEntry, role types.RegistryRole, assignment types.Assignment, newAddrs []string) ([]string, bool) { +// resolveRegistryRoleAddresses returns the addresses for a registry role under the given +// assignment. CURRENT* variants resolve the addresses currently held for the role; NEW* variants +// resolve the incoming new addresses. +// +// The singular ASSIGNMENT_CURRENT and ASSIGNMENT_NEW variants are documented to resolve exactly +// one address and return an error if more than one would resolve. +func (k Keeper) resolveRegistryRoleAddresses(entry *types.RegistryEntry, role types.RegistryRole, assignment types.Assignment, newAddrs []string) ([]string, bool, error) { switch assignment { case types.Assignment_ASSIGNMENT_CURRENT, types.Assignment_ASSIGNMENT_CURRENT_ALL, types.Assignment_ASSIGNMENT_CURRENT_ANY: + var current []string for _, re := range entry.Roles { if re.Role == role { - return re.Addresses, len(re.Addresses) > 0 + current = re.Addresses + break } } - return nil, false + if assignment == types.Assignment_ASSIGNMENT_CURRENT && len(current) > 1 { + return nil, false, types.NewErrCodeUnauthorized( + fmt.Sprintf("ASSIGNMENT_CURRENT for role %s resolved to %d addresses, expected exactly one", role.ShortString(), len(current)), + ) + } + return current, len(current) > 0, nil case types.Assignment_ASSIGNMENT_NEW, types.Assignment_ASSIGNMENT_NEW_ALL, types.Assignment_ASSIGNMENT_NEW_ANY: - return newAddrs, len(newAddrs) > 0 + if assignment == types.Assignment_ASSIGNMENT_NEW && len(newAddrs) > 1 { + return nil, false, types.NewErrCodeUnauthorized( + fmt.Sprintf("ASSIGNMENT_NEW for role %s resolved to %d addresses, expected exactly one", role.ShortString(), len(newAddrs)), + ) + } + return newAddrs, len(newAddrs) > 0, nil default: - return nil, false + return nil, false, nil } } // resolveNFTRoleAddresses returns the address(es) for an NFT-module role (e.g. the NFT owner). -func (k Keeper) resolveNFTRoleAddresses(ctx context.Context, entry *types.RegistryEntry, nftRole types.NftRole) ([]string, bool) { +// NftRole is read-only from the registry's perspective and may only be used with the +// ASSIGNMENT_CURRENT* variants; any other assignment is a policy misconfiguration and returns an +// error. +func (k Keeper) resolveNFTRoleAddresses(ctx context.Context, entry *types.RegistryEntry, nftRole types.NftRole, assignment types.Assignment) ([]string, bool, error) { + switch assignment { + case types.Assignment_ASSIGNMENT_CURRENT, + types.Assignment_ASSIGNMENT_CURRENT_ALL, + types.Assignment_ASSIGNMENT_CURRENT_ANY: + // NftRole is only valid with the CURRENT* assignment variants. + default: + return nil, false, types.NewErrCodeUnauthorized( + fmt.Sprintf("NftRole may only be used with ASSIGNMENT_CURRENT* variants, got %s", assignment.String()), + ) + } + switch nftRole { case types.NftRole_NFT_ROLE_NFT_OWNER: owner := k.GetNFTOwner(ctx, &entry.Key.AssetClassId, &entry.Key.NftId) if len(owner) == 0 { - return nil, false + return nil, false, nil } - return []string{owner.String()}, true + return []string{owner.String()}, true, nil default: - return nil, false + return nil, false, nil } } // resolveRolePriorityAddresses iterates priority entries and returns addresses from the first // non-empty role that exists, implementing the "first match wins" fallback chain. -func (k Keeper) resolveRolePriorityAddresses(ctx context.Context, entry *types.RegistryEntry, rp *types.RolePriority, assignment types.Assignment, newAddrs []string) ([]string, bool) { +func (k Keeper) resolveRolePriorityAddresses(ctx context.Context, entry *types.RegistryEntry, rp *types.RolePriority, assignment types.Assignment, newAddrs []string) ([]string, bool, error) { if rp == nil { - return nil, false + return nil, false, nil } for _, e := range rp.Entries { switch r := e.Role.(type) { case *types.RolePriorityEntry_RegistryRole: - addrs, exists := k.resolveRegistryRoleAddresses(entry, r.RegistryRole, assignment, newAddrs) + addrs, exists, err := k.resolveRegistryRoleAddresses(entry, r.RegistryRole, assignment, newAddrs) + if err != nil { + return nil, false, err + } if exists { - return addrs, true + return addrs, true, nil } case *types.RolePriorityEntry_NftRole: - addrs, exists := k.resolveNFTRoleAddresses(ctx, entry, r.NftRole) + addrs, exists, err := k.resolveNFTRoleAddresses(ctx, entry, r.NftRole, assignment) + if err != nil { + return nil, false, err + } if exists { - return addrs, true + return addrs, true, nil } } } - return nil, false + return nil, false, nil } // resolveRoleAssignmentAddresses dispatches to the correct resolver based on the role selector type. -func (k Keeper) resolveRoleAssignmentAddresses(ctx context.Context, entry *types.RegistryEntry, ra *types.RoleAssignment, newAddrs []string) ([]string, bool) { +func (k Keeper) resolveRoleAssignmentAddresses(ctx context.Context, entry *types.RegistryEntry, ra *types.RoleAssignment, newAddrs []string) ([]string, bool, error) { if ra == nil { - return nil, false + return nil, false, nil } switch r := ra.RoleSelector.(type) { case *types.RoleAssignment_RegistryRole: return k.resolveRegistryRoleAddresses(entry, r.RegistryRole, ra.Assignment, newAddrs) case *types.RoleAssignment_NftRole: - return k.resolveNFTRoleAddresses(ctx, entry, r.NftRole) + return k.resolveNFTRoleAddresses(ctx, entry, r.NftRole, ra.Assignment) case *types.RoleAssignment_RolePriority: return k.resolveRolePriorityAddresses(ctx, entry, r.RolePriority, ra.Assignment, newAddrs) default: - return nil, false + return nil, false, nil } } @@ -98,7 +134,10 @@ func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.R switch req.Type { case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL: for _, ra := range req.Roles { - addrs, exists := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + addrs, exists, err := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if err != nil { + return err + } if !exists || len(addrs) == 0 { return types.NewErrCodeUnauthorized("required role resolved to no addresses") } @@ -111,7 +150,10 @@ func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.R case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET: for _, ra := range req.Roles { - addrs, exists := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + addrs, exists, err := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if err != nil { + return err + } if !exists || len(addrs) == 0 { continue } @@ -125,7 +167,10 @@ func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.R case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY: found := false for _, ra := range req.Roles { - addrs, _ := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + addrs, _, err := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if err != nil { + return err + } for _, addr := range addrs { if signerSet[addr] { found = true @@ -144,7 +189,10 @@ func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.R hasAnyRole := false found := false for _, ra := range req.Roles { - addrs, exists := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + addrs, exists, err := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + if err != nil { + return err + } if !exists || len(addrs) == 0 { continue } @@ -209,3 +257,29 @@ func (k Keeper) ValidateRoleChangeAuthorization(ctx context.Context, roleAuth ty fmt.Sprintf("no valid authorization path found for %s: %s", roleAuth.Role.ShortString(), strings.Join(pathErrors, "; ")), ) } + +// CollectPolicyApprovers returns the set of addresses that the given role authorization policy +// could ever require to sign, resolved against the entry and the addresses being newly assigned. +// These are the only approvers whose signature can contribute to satisfying the policy; any other +// address is, by definition, a no-op approver. Resolution errors from a misconfigured path are +// ignored here (the path simply contributes no eligible addresses). +func (k Keeper) CollectPolicyApprovers(ctx context.Context, roleAuth types.RoleAuthorization, entry *types.RegistryEntry, newAddrs []string) map[string]bool { + out := make(map[string]bool) + for _, auth := range roleAuth.Authorizations { + if auth == nil { + continue + } + for _, req := range auth.Signatures { + if req == nil { + continue + } + for _, ra := range req.Roles { + addrs, _, _ := k.resolveRoleAssignmentAddresses(ctx, entry, ra, newAddrs) + for _, addr := range addrs { + out[addr] = true + } + } + } + } + return out +} diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 85cf83be1d..8919b9a248 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -301,11 +301,20 @@ func (k Keeper) SetRegistry(ctx context.Context, entry types.RegistryEntry) erro func (k Keeper) SetRoles(ctx context.Context, key *types.RegistryKey, roleUpdates []types.RoleUpdate) error { entry, err := k.Registry.Get(ctx, key.CollKey()) if err != nil { - return types.NewErrCodeRegistryNotFound(key.String()) + if errors.Is(err, collections.ErrNotFound) { + return types.NewErrCodeRegistryNotFound(key.String()) + } + return fmt.Errorf("failed to get registry entry: %w", err) } - // Snapshot the original entry to compute diff events. + // Snapshot the original entry to compute diff events. The loop below mutates entry.Roles + // (and individual Addresses slices) in place, so a shallow copy would corrupt the snapshot + // and produce incorrect diffs. Deep-copy the Roles slice and each Addresses slice. origCopy := entry + origCopy.Roles = make([]types.RolesEntry, len(entry.Roles)) + for i, re := range entry.Roles { + origCopy.Roles[i] = types.RolesEntry{Role: re.Role, Addresses: slices.Clone(re.Addresses)} + } for _, update := range roleUpdates { roleI := -1 diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index e54293268f..62291b4bd0 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -126,11 +126,15 @@ func (k Keeper) ApproveRoleChange(ctx context.Context, approver string, changeID // recordApprovalAndMaybeApply adds approver to the change's approval set, evaluates the role's // authorization policy against the accumulated approvals, and either applies and removes the // change (returns true) or persists the updated change (returns false). +// +// Approvals from addresses that are not eligible under any affected role's policy are silently +// ignored: they can never contribute to satisfying the change, so recording them would only let a +// third party grow the approval set without bound (state bloat / DoS). func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) (bool, error) { - if !slices.Contains(change.Approvals, approver) { + if k.approverEligible(ctx, entry, change, approver) && !slices.Contains(change.Approvals, approver) { change.Approvals = append(change.Approvals, approver) + k.EmitEvent(ctx, types.NewEventRoleChangeApproved(change.Id, approver)) } - k.EmitEvent(ctx, types.NewEventRoleChangeApproved(change.Id, approver)) if !k.pendingChangeSatisfied(ctx, entry, change) { if err := k.SetPendingRoleChange(ctx, *change); err != nil { @@ -149,6 +153,29 @@ func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.Re return true, nil } +// approverEligible reports whether approver could contribute to satisfying any of the change's +// role-update gates: for a policy-governed role they must be one of the policy's referenced +// addresses, and for a non-policy role they must own the NFT (the legacy fallback). +func (k Keeper) approverEligible(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) bool { + roleAuths := types.RoleAuthorizationMap() + for _, update := range change.RoleUpdates { + roleAuth, ok := roleAuths[update.Role] + if !ok { + // Non-policy role: only the NFT owner can satisfy the fallback. + if k.ValidateNFTOwner(ctx, &change.Key.AssetClassId, &change.Key.NftId, approver) == nil { + return true + } + continue + } + + newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) + if k.CollectPolicyApprovers(ctx, roleAuth, entry, newAddrs)[approver] { + return true + } + } + return false +} + // pendingChangeSatisfied reports whether the accumulated approvals satisfy every affected role's // authorization policy. A role with no policy falls back to NFT ownership: at least one approver // must own the NFT. The change applies only when all role updates are satisfied (atomic gate). diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index 7cca8432c8..536cc7b589 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -259,6 +259,14 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerSet_WithSecure applied := s.approve(changeID, s.stranger) s.Require().False(applied, "an unrelated approval does not satisfy the policy") s.Require().NotContains(s.roleAddresses(key, controllerRole), s.newController) + + // The stranger is not referenced by the controller policy, so its approval must be + // ignored entirely rather than accumulating in the pending change's approval set. + pending, err := s.registryKeeper.GetPendingRoleChange(s.ctx, changeID) + s.Require().NoError(err) + s.Require().NotNil(pending) + s.Require().NotContains(pending.Approvals, s.stranger, "ineligible approvals are not recorded") + s.Require().Equal([]string{s.currentController}, pending.Approvals) }) s.Run("incomplete: only current controller approves", func() { diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 0a6032bce7..e03767619c 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -100,8 +100,8 @@ func (m MsgSetRoles) ValidateBasic() error { errs = append(errs, NewErrCodeInvalidField("role_updates", "at least one role update is required")) } else { for i, update := range m.RoleUpdates { - if update.Role == RegistryRole_REGISTRY_ROLE_UNSPECIFIED { - errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role cannot be unspecified", i)) + if err := update.Role.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role %s", i, err)) } for _, addr := range update.Addresses { if _, err := sdk.AccAddressFromBech32(addr); err != nil { @@ -129,8 +129,8 @@ func (m MsgProposeRoleChange) ValidateBasic() error { errs = append(errs, NewErrCodeInvalidField("role_updates", "at least one role update is required")) } else { for i, update := range m.RoleUpdates { - if update.Role == RegistryRole_REGISTRY_ROLE_UNSPECIFIED { - errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role cannot be unspecified", i)) + if err := update.Role.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role %s", i, err)) } for _, addr := range update.Addresses { if _, err := sdk.AccAddressFromBech32(addr); err != nil { diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index b82fa787f1..2e1052faac 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -138,6 +138,7 @@ func TestMsgSetRoles_ValidateBasic(t *testing.T) { {name: "nil key", msg: MsgSetRoles{Signer: validAddr, Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: registry key cannot be nil"}, {name: "no role_updates", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, {name: "unspecified role", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, + {name: "unknown role enum", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole(999), Addresses: []string{otherAddr}}}}, exp: "invalid role_updates: 0: role unknown registry_role enum value: 999"}, {name: "bad address in update", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } @@ -171,6 +172,7 @@ func TestMsgProposeRoleChange_ValidateBasic(t *testing.T) { {name: "nil key", msg: MsgProposeRoleChange{Signer: validAddr, Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: registry key cannot be nil"}, {name: "no role_updates", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, {name: "unspecified role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, + {name: "unknown role enum", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole(999), Addresses: []string{otherAddr}}}}, exp: "invalid role_updates: 0: role unknown registry_role enum value: 999"}, {name: "bad address in update", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } diff --git a/x/registry/types/pending.go b/x/registry/types/pending.go index 5fcaf7cc65..69cd2290bc 100644 --- a/x/registry/types/pending.go +++ b/x/registry/types/pending.go @@ -21,6 +21,7 @@ func NewPendingRoleChangeID(key *RegistryKey, roleUpdates []RoleUpdate) string { for i, update := range roleUpdates { sorted := slices.Clone(update.Addresses) slices.Sort(sorted) + sorted = slices.Compact(sorted) lines[i] = strconv.Itoa(int(update.Role)) + "|" + strings.Join(sorted, ",") } slices.Sort(lines) From ecf2d9bc016c1941bc905d570433a042f692c5a4 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 14:18:55 -0600 Subject: [PATCH 10/36] Address second round of PR #1 review comments - Reject duplicate roles across the batch and duplicate addresses within a RoleUpdate in MsgSetRoles/MsgProposeRoleChange ValidateBasic (shared helper) - Fix propose-role-change CLI example to use secured_party_for_enote - Fail closed in evaluateSignatureRequirement on unspecified/unknown signature types - Fail closed in resolveRegistryRoleAddresses on unsupported/unspecified assignments - Fail closed in resolveRoleAssignmentAddresses when no role selector is set - Enforce exactly 0 or 2 args for the pending-role-changes query command Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/client/cli/query.go | 8 +++- x/registry/client/cli/tx.go | 2 +- x/registry/keeper/authorization.go | 16 +++++++- x/registry/types/msgs.go | 62 +++++++++++++++++------------- x/registry/types/msgs_test.go | 4 ++ 5 files changed, 62 insertions(+), 30 deletions(-) diff --git a/x/registry/client/cli/query.go b/x/registry/client/cli/query.go index 00271c336e..bc9f7075f3 100644 --- a/x/registry/client/cli/query.go +++ b/x/registry/client/cli/query.go @@ -2,6 +2,7 @@ package cli import ( "context" + "fmt" "github.com/spf13/cobra" @@ -177,7 +178,12 @@ func GetCmdQueryPendingRoleChanges() *cobra.Command { cmd := &cobra.Command{ Use: "pending-role-changes [asset_class_id] [nft_id]", Short: "Query pending role changes, optionally filtered by registry key", - Args: cobra.RangeArgs(0, 2), + Args: func(_ *cobra.Command, args []string) error { + if len(args) != 0 && len(args) != 2 { + return fmt.Errorf("accepts either 0 args (all pending changes) or 2 args (asset_class_id and nft_id), received %d", len(args)) + } + return nil + }, RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientQueryContext(cmd) if err != nil { diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index b8a14cae1b..68156bd93d 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -236,7 +236,7 @@ every affected role's authorization policy is satisfied, then applies atomically Each role update is "role=address[,address...]". An empty address list clears the role. Example: - propose-role-change myclass nft1 controller=cosmos1new secured_party_enote=`, + propose-role-change myclass nft1 controller=cosmos1new secured_party_for_enote=`, Args: cobra.MinimumNArgs(3), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) diff --git a/x/registry/keeper/authorization.go b/x/registry/keeper/authorization.go index b391c6c50b..fdb3287769 100644 --- a/x/registry/keeper/authorization.go +++ b/x/registry/keeper/authorization.go @@ -42,7 +42,11 @@ func (k Keeper) resolveRegistryRoleAddresses(entry *types.RegistryEntry, role ty } return newAddrs, len(newAddrs) > 0, nil default: - return nil, false, nil + // Fail closed: an unspecified/unknown assignment is a policy misconfiguration and must not + // silently skip a required signature check. + return nil, false, types.NewErrCodeUnauthorized( + fmt.Sprintf("unsupported assignment %s for role %s", assignment.String(), role.ShortString()), + ) } } @@ -116,7 +120,9 @@ func (k Keeper) resolveRoleAssignmentAddresses(ctx context.Context, entry *types case *types.RoleAssignment_RolePriority: return k.resolveRolePriorityAddresses(ctx, entry, r.RolePriority, ra.Assignment, newAddrs) default: - return nil, false, nil + // Fail closed: an unset/unknown role selector is a policy misconfiguration and must not + // silently skip a required signature check. + return nil, false, types.NewErrCodeUnauthorized("role assignment has no role selector set") } } @@ -210,6 +216,12 @@ func (k Keeper) evaluateSignatureRequirement(ctx context.Context, entry *types.R if hasAnyRole && !found { return types.NewErrCodeUnauthorized("no required signature found in any role") } + + default: + // Fail closed: an unspecified/unknown signature type must not be treated as satisfied. + return types.NewErrCodeUnauthorized( + fmt.Sprintf("unsupported signature requirement type %s", req.Type.String()), + ) } return nil } diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index e03767619c..7a842bffd2 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -96,20 +96,7 @@ func (m MsgSetRoles) ValidateBasic() error { errs = append(errs, NewErrCodeInvalidField("key", "%s", err)) } - if len(m.RoleUpdates) == 0 { - errs = append(errs, NewErrCodeInvalidField("role_updates", "at least one role update is required")) - } else { - for i, update := range m.RoleUpdates { - if err := update.Role.Validate(); err != nil { - errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role %s", i, err)) - } - for _, addr := range update.Addresses { - if _, err := sdk.AccAddressFromBech32(addr); err != nil { - errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: invalid address: %s", i, err)) - } - } - } - } + errs = append(errs, validateRoleUpdates(m.RoleUpdates)...) return errors.Join(errs...) } @@ -125,22 +112,45 @@ func (m MsgProposeRoleChange) ValidateBasic() error { errs = append(errs, NewErrCodeInvalidField("key", "%s", err)) } - if len(m.RoleUpdates) == 0 { - errs = append(errs, NewErrCodeInvalidField("role_updates", "at least one role update is required")) - } else { - for i, update := range m.RoleUpdates { - if err := update.Role.Validate(); err != nil { - errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role %s", i, err)) + errs = append(errs, validateRoleUpdates(m.RoleUpdates)...) + + return errors.Join(errs...) +} + +// validateRoleUpdates validates a batch of desired-state role updates. Each role must be a known, +// specified enum value and may appear at most once across the batch (the batch is a desired-state +// map, so a repeated role is ambiguous). Addresses within an update must be valid and free of +// duplicates, matching the invariants enforced by RolesEntry.Validate once the batch is applied. +func validateRoleUpdates(updates []RoleUpdate) []error { + if len(updates) == 0 { + return []error{NewErrCodeInvalidField("role_updates", "at least one role update is required")} + } + + var errs []error + seenRoles := make(map[RegistryRole]bool, len(updates)) + for i, update := range updates { + if err := update.Role.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: role %s", i, err)) + } else if seenRoles[update.Role] { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: duplicate role %s", i, update.Role.ShortString())) + } else { + seenRoles[update.Role] = true + } + + seenAddrs := make(map[string]bool, len(update.Addresses)) + for _, addr := range update.Addresses { + if _, err := sdk.AccAddressFromBech32(addr); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: invalid address: %s", i, err)) + continue } - for _, addr := range update.Addresses { - if _, err := sdk.AccAddressFromBech32(addr); err != nil { - errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: invalid address: %s", i, err)) - } + if seenAddrs[addr] { + errs = append(errs, NewErrCodeInvalidField("role_updates", "%d: duplicate address: %s", i, addr)) + continue } + seenAddrs[addr] = true } } - - return errors.Join(errs...) + return errs } // ValidateBasic validates the MsgApproveRoleChange message diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 2e1052faac..6fd6bd4894 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -139,6 +139,8 @@ func TestMsgSetRoles_ValidateBasic(t *testing.T) { {name: "no role_updates", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, {name: "unspecified role", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, {name: "unknown role enum", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole(999), Addresses: []string{otherAddr}}}}, exp: "invalid role_updates: 0: role unknown registry_role enum value: 999"}, + {name: "duplicate role", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr}}, {Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{validAddr}}}}, exp: "invalid role_updates: 1: duplicate role CONTROLLER"}, + {name: "duplicate address in update", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr, otherAddr}}}}, exp: "invalid role_updates: 0: duplicate address"}, {name: "bad address in update", msg: MsgSetRoles{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } @@ -173,6 +175,8 @@ func TestMsgProposeRoleChange_ValidateBasic(t *testing.T) { {name: "no role_updates", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, {name: "unspecified role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, {name: "unknown role enum", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole(999), Addresses: []string{otherAddr}}}}, exp: "invalid role_updates: 0: role unknown registry_role enum value: 999"}, + {name: "duplicate role", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr}}, {Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{validAddr}}}}, exp: "invalid role_updates: 1: duplicate role CONTROLLER"}, + {name: "duplicate address in update", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr, otherAddr}}}}, exp: "invalid role_updates: 0: duplicate address"}, {name: "bad address in update", msg: MsgProposeRoleChange{Signer: validAddr, Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, } From 8f288093e138671d73cc81fda2761fac8e81d17e Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 14:29:32 -0600 Subject: [PATCH 11/36] Address third round of PR #1 review comments - Reject new role-change proposals from proposers not eligible to approve any affected role, so pending-change state only grows from actionable changes - Fail closed in resolveNFTRoleAddresses on unspecified/unknown NftRole values Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/authorization.go | 6 ++++- x/registry/keeper/pending.go | 11 +++++++- ...ole_change_accumulation_acceptance_test.go | 27 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/x/registry/keeper/authorization.go b/x/registry/keeper/authorization.go index fdb3287769..59b1f3ee12 100644 --- a/x/registry/keeper/authorization.go +++ b/x/registry/keeper/authorization.go @@ -74,7 +74,11 @@ func (k Keeper) resolveNFTRoleAddresses(ctx context.Context, entry *types.Regist } return []string{owner.String()}, true, nil default: - return nil, false, nil + // Fail closed: an unspecified/unknown NftRole is a policy misconfiguration and must not + // silently skip a required signature check. + return nil, false, types.NewErrCodeUnauthorized( + fmt.Sprintf("unsupported NftRole %s", nftRole.String()), + ) } } diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index 62291b4bd0..7793144344 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -81,12 +81,21 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ return "", false, err } if change == nil { - change = &types.PendingRoleChange{ + newChange := &types.PendingRoleChange{ Id: id, Key: key, RoleUpdates: roleUpdates, Proposer: proposer, } + // Only open a new pending change when the proposer could actually contribute a valid + // approval to at least one affected role. This keeps state from growing via proposals + // opened by accounts that are not a required party for the change. + if !k.approverEligible(ctx, entry, newChange, proposer) { + return "", false, types.NewErrCodeUnauthorized( + "proposer " + proposer + " is not eligible to approve any affected role", + ) + } + change = newChange k.EmitEvent(ctx, types.NewEventRoleChangeProposed(change)) } diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index 536cc7b589..ebc465b284 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -573,6 +573,33 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestPendingChange_EdgeCases( s.Require().Contains(err.Error(), "pending role change") }) + s.Run("ineligible proposer cannot open a pending change", func() { + key := s.mintNFT("acc-edge-ineligible-proposer") + s.setupRegistry(key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.currentController}}, + {Role: securedPartyRole, Addresses: []string{s.securedParty}}, + }) + + // The stranger is not a required party for the controller policy, so it must not be able + // to open (and persist) a new pending change. + resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: s.stranger, + Key: key, + RoleUpdates: []types.RoleUpdate{{ + Role: controllerRole, + Addresses: []string{s.newController}, + }}, + }) + s.Require().Error(err) + s.Require().Contains(err.Error(), "not eligible") + s.Require().Nil(resp) + + // No pending change should have been persisted for this key. + changes, _, err := s.registryKeeper.GetPendingRoleChanges(s.ctx, nil, key) + s.Require().NoError(err) + s.Require().Empty(changes, "no pending change is created for an ineligible proposer") + }) + s.Run("registry removed underneath cleans up the pending change", func() { key := s.mintNFT("acc-edge-removed") s.setupRegistry(key, []types.RolesEntry{ From dd3c1cef96f02d393e1e22e392c02c82c304ace7 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 14:47:59 -0600 Subject: [PATCH 12/36] lint --- proto/provenance/registry/v1/tx.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index f0541530e0..ac9e364ecf 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -9,7 +9,6 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; import "provenance/registry/v1/registry.proto"; -import "provenance/registry/v1/authorization.proto"; // Msg defines the registry Msg service. // This service provides transaction functionality for managing registry entries and roles. From 3129f7d685cc19e03cf7288ccdfeb9001aa9f3d9 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 23 Jun 2026 14:49:09 -0600 Subject: [PATCH 13/36] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/authorization.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/proto/provenance/registry/v1/authorization.proto b/proto/provenance/registry/v1/authorization.proto index 7f031e8039..82bfe186a2 100644 --- a/proto/provenance/registry/v1/authorization.proto +++ b/proto/provenance/registry/v1/authorization.proto @@ -30,7 +30,8 @@ enum SignatureType { enum NftRole { // NFT_ROLE_UNSPECIFIED is the default/unset value. NFT_ROLE_UNSPECIFIED = 0; - // NFT_ROLE_NFT_OWNER is the owner of the NFT (value owner from the metadata module Scope). + // NFT_ROLE_NFT_OWNER is the owner of the NFT. + // For metadata-scope NFTs, this is the scope value owner; otherwise it's the owner from the x/nft module. NFT_ROLE_NFT_OWNER = 1; } From ce99eeb084db034c900cc2318626bc247b86f496 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Wed, 24 Jun 2026 14:43:23 -0600 Subject: [PATCH 14/36] include gogo protos for local dev --- protoBindings/bindings/java/build.gradle.kts | 6 +++--- protoBindings/bindings/kotlin/build.gradle.kts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/protoBindings/bindings/java/build.gradle.kts b/protoBindings/bindings/java/build.gradle.kts index dc6f6bc2c7..d2f3140a4a 100644 --- a/protoBindings/bindings/java/build.gradle.kts +++ b/protoBindings/bindings/java/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { tasks.jar { archiveBaseName.set("proto-${project.name}") - exclude("**/google/**") +// exclude("**/google/**") } tasks.withType { enabled = true } @@ -67,8 +67,8 @@ sourceSets.main { proto.srcDirs(protoDirs) // Exclude Google well-known types from compilation - proto.exclude("**/google/**") - proto.exclude(excludes) +// proto.exclude("**/google/**") +// proto.exclude(excludes) } // For more advanced options see: https://github.com/google/protobuf-gradle-plugin diff --git a/protoBindings/bindings/kotlin/build.gradle.kts b/protoBindings/bindings/kotlin/build.gradle.kts index 4bc443a94c..7f863abf63 100644 --- a/protoBindings/bindings/kotlin/build.gradle.kts +++ b/protoBindings/bindings/kotlin/build.gradle.kts @@ -64,7 +64,7 @@ spotless { tasks.jar { archiveBaseName.set("proto-${project.name}") - exclude("**/google/**") +// exclude("**/google/**") } tasks.withType { enabled = true } @@ -104,8 +104,8 @@ sourceSets.main { proto.srcDirs(protoDirs) // Exclude Google well-known types from compilation - proto.exclude("**/google/**") - proto.exclude(excludes) +// proto.exclude("**/google/**") +// proto.exclude(excludes) } // For more advanced options see: https://github.com/google/protobuf-gradle-plugin From 133126d9b151f95e490a4c14c656cfe270abaa64 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Wed, 24 Jun 2026 15:17:34 -0600 Subject: [PATCH 15/36] feat(registry): add registry classes for asset-class-level authorization Phase C1: introduce maintainer-managed RegistryClass config that overrides the module's static default authorization policy via two-tier resolution (class policy -> static default / NFT-owner fallback). - proto: RegistryClass message; registry_class_id on RegistryEntry and MsgRegisterNFT; MsgCreateRegistryClass and MsgUpdateRegistryClassRoleAuthorization; RegistryClass/RegistryClasses queries; class created/updated events; genesis registry_classes field - types: RegistryClass validation; new error codes; AllRequestMsgs and ValidateBasic for the new messages; genesis validation with duplicate-id check - keeper: registry class CRUD and roleAuthorizationsForEntry two-tier resolver; class-existence checks in RegisterNFT and RegistryBulkUpdate; CreateRegistry gains a registryClassID parameter; genesis and query-server handlers - cli: create/update class tx commands and class queries - tests: registry_class_acceptance_test covering class CRUD, maintainer enforcement, two-tier resolution, and genesis round-trip Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../registry/v1/authorization.proto | 22 + proto/provenance/registry/v1/events.proto | 13 + proto/provenance/registry/v1/genesis.proto | 4 + proto/provenance/registry/v1/query.proto | 38 + proto/provenance/registry/v1/registry.proto | 5 + proto/provenance/registry/v1/tx.proto | 61 +- x/registry/client/cli/query.go | 64 + x/registry/client/cli/tx.go | 100 ++ x/registry/keeper/class.go | 114 ++ x/registry/keeper/genesis.go | 13 + x/registry/keeper/genesis_test.go | 8 +- x/registry/keeper/keeper.go | 18 +- x/registry/keeper/keeper_test.go | 14 +- x/registry/keeper/keys.go | 1 + x/registry/keeper/msg_server.go | 53 +- x/registry/keeper/pending.go | 4 +- x/registry/keeper/query_server.go | 41 + x/registry/keeper/query_server_test.go | 6 +- .../keeper/registry_class_acceptance_test.go | 308 ++++ ...ole_change_accumulation_acceptance_test.go | 2 +- x/registry/types/authorization.go | 7 +- x/registry/types/authorization.pb.go | 455 +++++- x/registry/types/class.go | 67 + x/registry/types/errors.go | 12 + x/registry/types/events.go | 17 + x/registry/types/events.pb.go | 549 +++++++- x/registry/types/genesis.go | 11 + x/registry/types/genesis.pb.go | 102 +- x/registry/types/msgs.go | 68 + x/registry/types/msgs_test.go | 2 + x/registry/types/query.pb.go | 1077 ++++++++++++-- x/registry/types/query.pb.gw.go | 184 +++ x/registry/types/registry.pb.go | 138 +- x/registry/types/tx.pb.go | 1247 +++++++++++++++-- 34 files changed, 4471 insertions(+), 354 deletions(-) create mode 100644 x/registry/keeper/class.go create mode 100644 x/registry/keeper/registry_class_acceptance_test.go create mode 100644 x/registry/types/class.go diff --git a/proto/provenance/registry/v1/authorization.proto b/proto/provenance/registry/v1/authorization.proto index 82bfe186a2..7d87184a5f 100644 --- a/proto/provenance/registry/v1/authorization.proto +++ b/proto/provenance/registry/v1/authorization.proto @@ -5,8 +5,30 @@ option go_package = "github.com/provenance-io/provenance/x/registry/typ option java_package = "io.provenance.registry.v1"; option java_multiple_files = true; +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; import "provenance/registry/v1/registry.proto"; +// RegistryClass provides asset class-level authorization over role updates. +// It defines authorization rules for an entire NFT collection (asset class), allowing the +// maintainer to establish custom role update policies without requiring governance proposals. +message RegistryClass { + // registry_class_id is the unique identifier for this registry class (e.g. "dart-loan-v1"). + // It is required since NFT classes do not have an inherent owner, and must be unique across + // all registry classes. + string registry_class_id = 1 [(gogoproto.jsontag) = "registryClassId,omitempty"]; + + // asset_class_id is the Scope Specification ID or NFT Class ID this registry class governs. + string asset_class_id = 2; + + // maintainer is the address authorized to manage this registry class. It is permanent and + // cannot be changed once set. + string maintainer = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // role_authorizations are the authorization rules for role updates within this asset class. + repeated RoleAuthorization role_authorizations = 4 [(gogoproto.nullable) = false]; +} + // SignatureType defines how signature requirements are evaluated. enum SignatureType { // SIGNATURE_TYPE_UNSPECIFIED is the default/unset value. diff --git a/proto/provenance/registry/v1/events.proto b/proto/provenance/registry/v1/events.proto index 4bd5c5abc3..f888c1f4ba 100644 --- a/proto/provenance/registry/v1/events.proto +++ b/proto/provenance/registry/v1/events.proto @@ -59,3 +59,16 @@ message EventRoleChangeApplied { string asset_class_id = 2; string change_id = 3; } + +// EventRegistryClassCreated is emitted when a registry class is created. +message EventRegistryClassCreated { + string registry_class_id = 1; + string asset_class_id = 2; + string maintainer = 3; +} + +// EventRegistryClassUpdated is emitted when a registry class's authorization rules are updated. +message EventRegistryClassUpdated { + string registry_class_id = 1; + string maintainer = 2; +} diff --git a/proto/provenance/registry/v1/genesis.proto b/proto/provenance/registry/v1/genesis.proto index 0ecc3fa5ed..6ac3617b01 100644 --- a/proto/provenance/registry/v1/genesis.proto +++ b/proto/provenance/registry/v1/genesis.proto @@ -7,6 +7,7 @@ option java_multiple_files = true; import "gogoproto/gogo.proto"; import "provenance/registry/v1/registry.proto"; +import "provenance/registry/v1/authorization.proto"; // GenesisState defines the registry module's genesis state. // This contains all the registry entries that exist when the blockchain is first initialized. @@ -18,4 +19,7 @@ message GenesisState { // pending_role_changes is the list of role changes awaiting approval. // These preserve in-flight multi-party role changes across genesis export/import. repeated PendingRoleChange pending_role_changes = 2 [(gogoproto.nullable) = false]; + + // registry_classes is the list of registry classes defining asset class-level authorization rules. + repeated RegistryClass registry_classes = 3 [(gogoproto.nullable) = false]; } \ No newline at end of file diff --git a/proto/provenance/registry/v1/query.proto b/proto/provenance/registry/v1/query.proto index be5c78bce1..a4c2da8c3a 100644 --- a/proto/provenance/registry/v1/query.proto +++ b/proto/provenance/registry/v1/query.proto @@ -9,6 +9,7 @@ import "cosmos/base/query/v1beta1/pagination.proto"; import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "provenance/registry/v1/registry.proto"; +import "provenance/registry/v1/authorization.proto"; // Query defines the gRPC querier service for the registry module. // This service provides read-only access to registry data and role verification. @@ -40,6 +41,16 @@ service Query { rpc PendingRoleChanges(QueryPendingRoleChangesRequest) returns (QueryPendingRoleChangesResponse) { option (google.api.http).get = "/provenance/registry/v1/pending_role_changes"; } + + // RegistryClass returns a single registry class (including its authorization policy) by id. + rpc RegistryClass(QueryRegistryClassRequest) returns (QueryRegistryClassResponse) { + option (google.api.http).get = "/provenance/registry/v1/registry_class/{registry_class_id}"; + } + + // RegistryClasses returns all registry classes. + rpc RegistryClasses(QueryRegistryClassesRequest) returns (QueryRegistryClassesResponse) { + option (google.api.http).get = "/provenance/registry/v1/registry_classes"; + } } // QueryGetRegistryRequest is the request type for the Query/GetRegistry RPC method. @@ -135,3 +146,30 @@ message QueryPendingRoleChangesResponse { // pagination is the pagination details for this response. cosmos.base.query.v1beta1.PageResponse pagination = 99; } + +// QueryRegistryClassRequest is the request type for the Query/RegistryClass RPC method. +message QueryRegistryClassRequest { + // registry_class_id is the unique identifier of the registry class to retrieve. + string registry_class_id = 1; +} + +// QueryRegistryClassResponse is the response type for the Query/RegistryClass RPC method. +message QueryRegistryClassResponse { + // registry_class is the registry class for the requested id, including its authorization policy. + RegistryClass registry_class = 1 [(gogoproto.nullable) = false]; +} + +// QueryRegistryClassesRequest is the paginated request type for the Query/RegistryClasses RPC method. +message QueryRegistryClassesRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 99; +} + +// QueryRegistryClassesResponse is the paginated response type for the Query/RegistryClasses RPC method. +message QueryRegistryClassesResponse { + // registry_classes is the collection of registry classes. + repeated RegistryClass registry_classes = 1 [(gogoproto.nullable) = false]; + + // pagination is the pagination details for this response. + cosmos.base.query.v1beta1.PageResponse pagination = 99; +} diff --git a/proto/provenance/registry/v1/registry.proto b/proto/provenance/registry/v1/registry.proto index 12e9328a9f..68c7e70c78 100644 --- a/proto/provenance/registry/v1/registry.proto +++ b/proto/provenance/registry/v1/registry.proto @@ -72,6 +72,11 @@ message RegistryEntry { // roles is a list of roles and addresses that can perform that role. // Each role entry contains a role type and the addresses authorized for that role. repeated RolesEntry roles = 2 [(gogoproto.nullable) = false]; + + // registry_class_id is the ID of the registry class governing this entry's authorization rules. + // If set, role updates for this entry use the authorization rules defined in the referenced + // registry class. If empty, role updates fall back to legacy NFT owner authorization. + string registry_class_id = 3 [(gogoproto.jsontag) = "registryClassId,omitempty"]; } // RolesEntry represents a mapping between a role and the addresses that can perform that role. diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index ac9e364ecf..04663b77da 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -9,6 +9,7 @@ import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; import "provenance/registry/v1/registry.proto"; +import "provenance/registry/v1/authorization.proto"; // Msg defines the registry Msg service. // This service provides transaction functionality for managing registry entries and roles. @@ -52,6 +53,15 @@ service Msg { // ApproveRoleChange records a single-signer approval for an open pending role change. // When the accumulated approvals satisfy the role's policy, the change is applied automatically. rpc ApproveRoleChange(MsgApproveRoleChange) returns (MsgApproveRoleChangeResponse); + + // CreateRegistryClass creates a new registry class that defines asset class-level authorization + // rules for role updates. The signer must match the maintainer address. + rpc CreateRegistryClass(MsgCreateRegistryClass) returns (MsgCreateRegistryClassResponse); + + // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry + // class. Only the current maintainer may update the rules. + rpc UpdateRegistryClassRoleAuthorization(MsgUpdateRegistryClassRoleAuthorization) + returns (MsgUpdateRegistryClassRoleAuthorizationResponse); } // MsgRegisterNFT represents a message to register a new NFT in the registry. @@ -70,6 +80,10 @@ message MsgRegisterNFT { // roles is a list of roles and addresses that can perform that role. // Each role entry defines a role type and the addresses authorized for that role. repeated RolesEntry roles = 3 [(gogoproto.nullable) = false]; + + // registry_class_id optionally associates the new registry entry with a registry class whose + // authorization rules govern subsequent role updates. If set, the referenced class must exist. + string registry_class_id = 4 [(gogoproto.jsontag) = "registryClassId,omitempty"]; } // MsgRegisterNFTResponse defines the response for RegisterNFT. @@ -233,4 +247,49 @@ message MsgApproveRoleChange { message MsgApproveRoleChangeResponse { // applied is true if this approval satisfied the policy and the change was applied. bool applied = 1; -} \ No newline at end of file +} + +// MsgCreateRegistryClass creates a new registry class defining asset class-level authorization +// rules for role updates within an NFT collection. +message MsgCreateRegistryClass { + option (cosmos.msg.v1.signer) = "signer"; + + // signer must match the maintainer address. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // registry_class_id is the unique identifier for the registry class. Must be unique across all + // registry classes. + string registry_class_id = 2; + + // asset_class_id is the Scope Specification ID or NFT Class ID this registry class governs. + string asset_class_id = 3; + + // maintainer is the address authorized to manage this registry class. It is permanent and + // cannot be changed after creation. + string maintainer = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // role_authorizations are the initial authorization rules for this asset class. + repeated RoleAuthorization role_authorizations = 5 [(gogoproto.nullable) = false]; +} + +// MsgCreateRegistryClassResponse defines the response for CreateRegistryClass. +message MsgCreateRegistryClassResponse {} + +// MsgUpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing +// registry class. The new rules completely replace the existing rules (not a merge). +message MsgUpdateRegistryClassRoleAuthorization { + option (cosmos.msg.v1.signer) = "signer"; + + // signer must be the current maintainer address. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // registry_class_id identifies the registry class to update. + string registry_class_id = 2; + + // role_authorizations are the updated authorization rules (replaces existing rules). + repeated RoleAuthorization role_authorizations = 3 [(gogoproto.nullable) = false]; +} + +// MsgUpdateRegistryClassRoleAuthorizationResponse defines the response for +// UpdateRegistryClassRoleAuthorization. +message MsgUpdateRegistryClassRoleAuthorizationResponse {} \ No newline at end of file diff --git a/x/registry/client/cli/query.go b/x/registry/client/cli/query.go index bc9f7075f3..32d657a79e 100644 --- a/x/registry/client/cli/query.go +++ b/x/registry/client/cli/query.go @@ -28,6 +28,8 @@ func CmdQuery() *cobra.Command { GetCmdQueryHasRole(), GetCmdQueryPendingRoleChange(), GetCmdQueryPendingRoleChanges(), + GetCmdQueryRegistryClass(), + GetCmdQueryRegistryClasses(), ) return cmd @@ -221,3 +223,65 @@ func GetCmdQueryPendingRoleChanges() *cobra.Command { flags.AddPaginationFlagsToCmd(cmd, "pending-role-changes") return cmd } + +// GetCmdQueryRegistryClass returns the command for querying a single registry class by id +func GetCmdQueryRegistryClass() *cobra.Command { + cmd := &cobra.Command{ + Use: "registry-class ", + Short: "Query a registry class (including its authorization policy) by id", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.RegistryClass(context.Background(), &types.QueryRegistryClassRequest{ + RegistryClassId: args[0], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} + +// GetCmdQueryRegistryClasses returns the command for querying all registry classes +func GetCmdQueryRegistryClasses() *cobra.Command { + cmd := &cobra.Command{ + Use: "registry-classes", + Short: "Query all registry classes", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + pageReq, err := client.ReadPageRequestWithPageKeyDecoded(cmd.Flags()) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.RegistryClasses(context.Background(), &types.QueryRegistryClassesRequest{ + Pagination: pageReq, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "registry-classes") + return cmd +} diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index 68156bd93d..578c801ca6 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -33,6 +33,8 @@ func CmdTx() *cobra.Command { CmdRegistryBulkUpdate(), CmdProposeRoleChange(), CmdApproveRoleChange(), + CmdCreateRegistryClass(), + CmdUpdateRegistryClassRoleAuthorization(), ) return cmd @@ -291,6 +293,104 @@ func CmdApproveRoleChange() *cobra.Command { return cmd } +// CmdCreateRegistryClass returns the command to create a registry class. +// The class definition (including role_authorizations) is read from a JSON file in proto-JSON +// format so that oneof fields in the authorization policy are decoded correctly. +func CmdCreateRegistryClass() *cobra.Command { + cmd := &cobra.Command{ + Use: "create-registry-class ", + Short: "Create a registry class from a JSON file", + Long: `Create a registry class defining asset class-level authorization rules. + +The file is a proto-JSON RegistryClass, e.g.: +{ + "registryClassId": "dart-loan-v1", + "asset_class_id": "loan.asset", + "maintainer": "pb1...", + "role_authorizations": [ { "role": "REGISTRY_ROLE_CONTROLLER", "authorizations": [ ... ] } ] +} + +If "maintainer" is omitted it defaults to the --from signer. The signer must equal the maintainer.`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + jsonData, err := os.ReadFile(args[0]) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + var class types.RegistryClass + if err := clientCtx.Codec.UnmarshalJSON(jsonData, &class); err != nil { + return fmt.Errorf("failed to unmarshal registry class JSON: %w", err) + } + + signer := clientCtx.GetFromAddress().String() + maintainer := class.Maintainer + if maintainer == "" { + maintainer = signer + } + + msg := types.MsgCreateRegistryClass{ + Signer: signer, + RegistryClassId: class.RegistryClassId, + AssetClassId: class.AssetClassId, + Maintainer: maintainer, + RoleAuthorizations: class.RoleAuthorizations, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +// CmdUpdateRegistryClassRoleAuthorization returns the command to replace a registry class's +// authorization rules. The new rules are read from a proto-JSON RegistryClass file (only its +// role_authorizations are used). +func CmdUpdateRegistryClassRoleAuthorization() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-registry-class ", + Short: "Replace a registry class's authorization rules from a JSON file", + Long: `Replace the authorization rules for an existing registry class. Only the current +maintainer may update the rules. The file is a proto-JSON RegistryClass; only its +role_authorizations field is used.`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + jsonData, err := os.ReadFile(args[1]) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + var class types.RegistryClass + if err := clientCtx.Codec.UnmarshalJSON(jsonData, &class); err != nil { + return fmt.Errorf("failed to unmarshal registry class JSON: %w", err) + } + + msg := types.MsgUpdateRegistryClassRoleAuthorization{ + Signer: clientCtx.GetFromAddress().String(), + RegistryClassId: args[0], + RoleAuthorizations: class.RoleAuthorizations, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + // parseRoleUpdates converts "role=address[,address...]" arguments into RoleUpdate values. // An empty address list (e.g. "role=") clears the role. func parseRoleUpdates(args []string) ([]types.RoleUpdate, error) { diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go new file mode 100644 index 0000000000..7fb1920838 --- /dev/null +++ b/x/registry/keeper/class.go @@ -0,0 +1,114 @@ +package keeper + +import ( + "context" + "errors" + "fmt" + + "cosmossdk.io/collections" + "github.com/cosmos/cosmos-sdk/types/query" + + "github.com/provenance-io/provenance/x/registry/types" +) + +// GetRegistryClass returns the registry class for the given id. If no class exists for the id, it +// returns nil, nil. +func (k Keeper) GetRegistryClass(ctx context.Context, registryClassID string) (*types.RegistryClass, error) { + class, err := k.RegistryClasses.Get(ctx, registryClassID) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return nil, nil + } + return nil, err + } + return &class, nil +} + +// HasRegistryClass reports whether a registry class exists for the given id. +func (k Keeper) HasRegistryClass(ctx context.Context, registryClassID string) (bool, error) { + return k.RegistryClasses.Has(ctx, registryClassID) +} + +// SetRegistryClass stores a registry class in state. +func (k Keeper) SetRegistryClass(ctx context.Context, class types.RegistryClass) error { + return k.RegistryClasses.Set(ctx, class.RegistryClassId, class) +} + +// CreateRegistryClass stores a new registry class. It returns an error if a class already exists +// with the same id. +func (k Keeper) CreateRegistryClass(ctx context.Context, class types.RegistryClass) error { + has, err := k.RegistryClasses.Has(ctx, class.RegistryClassId) + if err != nil { + return fmt.Errorf("could not check if registry class exists: %w", err) + } + if has { + return types.NewErrCodeRegistryClassExists(class.RegistryClassId) + } + + if err = k.SetRegistryClass(ctx, class); err != nil { + return fmt.Errorf("could not set registry class: %w", err) + } + + k.EmitEvent(ctx, types.NewEventRegistryClassCreated(&class)) + return nil +} + +// UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry +// class. Only the current maintainer (the provided signer) may update the rules. The maintainer +// and asset class id are immutable. +func (k Keeper) UpdateRegistryClassRoleAuthorization(ctx context.Context, signer, registryClassID string, roleAuths []types.RoleAuthorization) error { + class, err := k.GetRegistryClass(ctx, registryClassID) + if err != nil { + return fmt.Errorf("could not get registry class: %w", err) + } + if class == nil { + return types.NewErrCodeRegistryClassNotFound(registryClassID) + } + + if signer != class.Maintainer { + return types.NewErrCodeUnauthorized( + fmt.Sprintf("signer %q is not the maintainer of registry class %q", signer, registryClassID), + ) + } + + class.RoleAuthorizations = roleAuths + if err = k.SetRegistryClass(ctx, *class); err != nil { + return fmt.Errorf("could not set registry class: %w", err) + } + + k.EmitEvent(ctx, types.NewEventRegistryClassUpdated(class)) + return nil +} + +// GetRegistryClasses returns the registry classes paginated. +func (k Keeper) GetRegistryClasses(ctx context.Context, pagination *query.PageRequest) ([]types.RegistryClass, *query.PageResponse, error) { + ptrs, pageRes, err := query.CollectionPaginate(ctx, k.RegistryClasses, pagination, func(_ string, class types.RegistryClass) (*types.RegistryClass, error) { + return &class, nil + }) + if err != nil { + return nil, nil, err + } + + classes := make([]types.RegistryClass, len(ptrs)) + for i, p := range ptrs { + classes[i] = *p + } + return classes, pageRes, nil +} + +// roleAuthorizationsForEntry resolves the effective role authorization policies that govern role +// updates for the given registry entry, implementing the two-tier resolution: +// +// 1. Registry class level (highest priority): if the entry references a registry class that +// exists, use the authorization rules defined by that class. +// 2. Static default (fallback): otherwise use the module's static default policies. Roles not +// covered by the returned map fall back to legacy NFT-owner authorization at the call site. +func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.RegistryEntry) map[types.RegistryRole]types.RoleAuthorization { + if entry != nil && entry.RegistryClassId != "" { + class, err := k.GetRegistryClass(ctx, entry.RegistryClassId) + if err == nil && class != nil { + return types.RoleAuthorizationMapFrom(class.RoleAuthorizations) + } + } + return types.RoleAuthorizationMap() +} diff --git a/x/registry/keeper/genesis.go b/x/registry/keeper/genesis.go index 6cc9059bad..30a51bec5b 100644 --- a/x/registry/keeper/genesis.go +++ b/x/registry/keeper/genesis.go @@ -19,6 +19,11 @@ func (k Keeper) InitGenesis(ctx context.Context, state *types.GenesisState) { panic(err) // Genesis should not fail } } + for _, class := range state.RegistryClasses { + if err := k.RegistryClasses.Set(ctx, class.RegistryClassId, class); err != nil { + panic(err) // Genesis should not fail + } + } } func (k Keeper) ExportGenesis(ctx context.Context) *types.GenesisState { @@ -40,5 +45,13 @@ func (k Keeper) ExportGenesis(ctx context.Context) *types.GenesisState { panic(err) } + err = k.RegistryClasses.Walk(ctx, nil, func(_ string, value types.RegistryClass) (stop bool, err error) { + genesis.RegistryClasses = append(genesis.RegistryClasses, value) + return false, nil + }) + if err != nil { + panic(err) + } + return genesis } diff --git a/x/registry/keeper/genesis_test.go b/x/registry/keeper/genesis_test.go index 6a90410f97..4cd900042e 100644 --- a/x/registry/keeper/genesis_test.go +++ b/x/registry/keeper/genesis_test.go @@ -224,7 +224,7 @@ func (s *KeeperTestSuite) TestExportGenesis() { Addresses: []string{s.user1}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles, "") s.Require().NoError(err) }, expEntries: []types.RegistryEntry{ @@ -276,7 +276,7 @@ func (s *KeeperTestSuite) TestExportGenesis() { } for _, e := range entries { - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, e.key, e.roles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, e.key, e.roles, "") s.Require().NoError(err) } }, @@ -377,9 +377,9 @@ func (s *KeeperTestSuite) TestGenesisRoundTrip() { } // Create registries - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key1, roles1) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key1, roles1, "") s.Require().NoError(err) - err = s.app.RegistryKeeper.CreateRegistry(s.ctx, key2, roles2) + err = s.app.RegistryKeeper.CreateRegistry(s.ctx, key2, roles2, "") s.Require().NoError(err) // Export genesis diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 8919b9a248..bbb91d3ba5 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -23,6 +23,7 @@ type Keeper struct { schema collections.Schema Registry collections.Map[collections.Pair[string, string], types.RegistryEntry] PendingRoleChanges collections.Map[string, types.PendingRoleChange] + RegistryClasses collections.Map[string, types.RegistryClass] NFTKeeper NFTKeeper MetadataKeeper MetadataKeeper @@ -51,6 +52,14 @@ func NewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, nftKeep codec.CollValue[types.PendingRoleChange](cdc), ), + RegistryClasses: collections.NewMap( + sb, + collections.NewPrefix(registryClassPrefix), + "registry_classes", + collections.StringKey, + codec.CollValue[types.RegistryClass](cdc), + ), + NFTKeeper: nftKeeper, MetadataKeeper: metaDataKeeper, } @@ -94,12 +103,12 @@ func (k Keeper) CreateDefaultRegistry(ctx context.Context, ownerAddrStr string, Addresses: []string{ownerAddrStr}, } - return k.CreateRegistry(ctx, key, roles) + return k.CreateRegistry(ctx, key, roles, "") } // CreateRegistry stores a new registry entry in state. // Returns an error if the registry already exists, or if there's a problem. -func (k Keeper) CreateRegistry(ctx context.Context, key *types.RegistryKey, roles []types.RolesEntry) error { +func (k Keeper) CreateRegistry(ctx context.Context, key *types.RegistryKey, roles []types.RolesEntry, registryClassID string) error { // Already exists check has, err := k.Registry.Has(ctx, key.CollKey()) if err != nil { @@ -110,8 +119,9 @@ func (k Keeper) CreateRegistry(ctx context.Context, key *types.RegistryKey, role } err = k.Registry.Set(ctx, key.CollKey(), types.RegistryEntry{ - Key: key, - Roles: roles, + Key: key, + Roles: roles, + RegistryClassId: registryClassID, }) if err != nil { return fmt.Errorf("could not set registry entry: %w", err) diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index d27f55384c..9d03d6a08d 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -116,7 +116,7 @@ func (s *KeeperTestSuite) TestCreateRegistry() { }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles, "") s.Require().NoError(err) // Verify registry was created @@ -128,7 +128,7 @@ func (s *KeeperTestSuite) TestCreateRegistry() { // Test duplicate creation not allowed. expDupErr := "registry already exists for key: \"" + key.String() + "\": registry already exists" - err = s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles) + err = s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles, "") s.Require().EqualError(err, expDupErr) } @@ -144,7 +144,7 @@ func (s *KeeperTestSuite) TestGrantRole() { Addresses: []string{s.user1Addr.String()}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles, "") s.Require().NoError(err) tests := []struct { @@ -239,7 +239,7 @@ func (s *KeeperTestSuite) TestRevokeRole() { Addresses: []string{s.user1Addr.String(), s.user2Addr.String()}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles, "") s.Require().NoError(err) tests := []struct { @@ -322,7 +322,7 @@ func (s *KeeperTestSuite) TestHasRole() { Addresses: []string{s.user1Addr.String()}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles, "") s.Require().NoError(err) tests := []struct { @@ -387,7 +387,7 @@ func (s *KeeperTestSuite) TestGetRegistry() { Addresses: []string{s.user1Addr.String()}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, baseKey, baseRoles, "") s.Require().NoError(err) tests := []struct { @@ -456,7 +456,7 @@ func (s *KeeperTestSuite) TestGetRegistries() { {AssetClassId: "eclass", NftId: "nft1"}, } for _, k := range keys { - s.Require().NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, k, roles)) + s.Require().NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, k, roles, "")) } // Helper to get key strings in order diff --git a/x/registry/keeper/keys.go b/x/registry/keeper/keys.go index 63463827dd..5168f441d1 100644 --- a/x/registry/keeper/keys.go +++ b/x/registry/keeper/keys.go @@ -3,4 +3,5 @@ package keeper var ( registryPrefix = []byte{0x01} pendingRoleChangePrefix = []byte{0x02} + registryClassPrefix = []byte{0x03} ) diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index d95783da88..365f278344 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -34,8 +34,19 @@ func (k msgServer) RegisterNFT(ctx context.Context, msg *types.MsgRegisterNFT) ( return nil, err } + // If a registry class is referenced, it must exist. + if msg.RegistryClassId != "" { + has, err := k.HasRegistryClass(ctx, msg.RegistryClassId) + if err != nil { + return nil, err + } + if !has { + return nil, types.NewErrCodeRegistryClassNotFound(msg.RegistryClassId) + } + } + // Store the registry in state. - err := k.CreateRegistry(ctx, msg.Key, msg.Roles) + err := k.CreateRegistry(ctx, msg.Key, msg.Roles, msg.RegistryClassId) if err != nil { return nil, err } @@ -55,7 +66,7 @@ func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*typ return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) } - roleAuths := types.RoleAuthorizationMap() + roleAuths := k.roleAuthorizationsForEntry(ctx, entry) if roleAuth, ok := roleAuths[msg.Role]; ok { // Policy-based path: the incoming addresses are the "new" assignment. A single signer must // satisfy the policy on its own; multi-party changes go through ProposeRoleChange. @@ -90,7 +101,7 @@ func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*t return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) } - roleAuths := types.RoleAuthorizationMap() + roleAuths := k.roleAuthorizationsForEntry(ctx, entry) if roleAuth, ok := roleAuths[msg.Role]; ok { // Policy-based path. For revoke, no new addresses are being assigned. A single signer must // satisfy the policy on its own; multi-party changes go through ProposeRoleChange. @@ -126,7 +137,7 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) } - roleAuths := types.RoleAuthorizationMap() + roleAuths := k.roleAuthorizationsForEntry(ctx, entry) for _, update := range msg.RoleUpdates { if roleAuth, ok := roleAuths[update.Role]; ok { // The new addresses for this role are the desired state from the update. A single signer @@ -169,6 +180,29 @@ func (k msgServer) ApproveRoleChange(ctx context.Context, msg *types.MsgApproveR return &types.MsgApproveRoleChangeResponse{Applied: applied}, nil } +// CreateRegistryClass creates a new registry class defining asset class-level authorization rules. +func (k msgServer) CreateRegistryClass(ctx context.Context, msg *types.MsgCreateRegistryClass) (*types.MsgCreateRegistryClassResponse, error) { + class := types.RegistryClass{ + RegistryClassId: msg.RegistryClassId, + AssetClassId: msg.AssetClassId, + Maintainer: msg.Maintainer, + RoleAuthorizations: msg.RoleAuthorizations, + } + if err := k.Keeper.CreateRegistryClass(ctx, class); err != nil { + return nil, err + } + return &types.MsgCreateRegistryClassResponse{}, nil +} + +// UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry +// class. Only the current maintainer may update the rules. +func (k msgServer) UpdateRegistryClassRoleAuthorization(ctx context.Context, msg *types.MsgUpdateRegistryClassRoleAuthorization) (*types.MsgUpdateRegistryClassRoleAuthorizationResponse, error) { + if err := k.Keeper.UpdateRegistryClassRoleAuthorization(ctx, msg.Signer, msg.RegistryClassId, msg.RoleAuthorizations); err != nil { + return nil, err + } + return &types.MsgUpdateRegistryClassRoleAuthorizationResponse{}, nil +} + // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry and associated data for the specified key. func (k msgServer) UnregisterNFT(ctx context.Context, msg *types.MsgUnregisterNFT) (*types.MsgUnregisterNFTResponse, error) { @@ -206,6 +240,17 @@ func (k msgServer) RegistryBulkUpdate(ctx context.Context, msg *types.MsgRegistr } } + // If a registry class is referenced, it must exist. + if entry.RegistryClassId != "" { + has, err := k.HasRegistryClass(ctx, entry.RegistryClassId) + if err != nil { + return nil, fmt.Errorf("[%d]: %w", i, err) + } + if !has { + return nil, fmt.Errorf("[%d]: %w", i, types.NewErrCodeRegistryClassNotFound(entry.RegistryClassId)) + } + } + // Get the original registry, so we know what we're updating. orig, err := k.GetRegistry(ctx, entry.Key) if err != nil { diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index 7793144344..b366432f50 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -166,7 +166,7 @@ func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.Re // role-update gates: for a policy-governed role they must be one of the policy's referenced // addresses, and for a non-policy role they must own the NFT (the legacy fallback). func (k Keeper) approverEligible(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) bool { - roleAuths := types.RoleAuthorizationMap() + roleAuths := k.roleAuthorizationsForEntry(ctx, entry) for _, update := range change.RoleUpdates { roleAuth, ok := roleAuths[update.Role] if !ok { @@ -189,7 +189,7 @@ func (k Keeper) approverEligible(ctx context.Context, entry *types.RegistryEntry // authorization policy. A role with no policy falls back to NFT ownership: at least one approver // must own the NFT. The change applies only when all role updates are satisfied (atomic gate). func (k Keeper) pendingChangeSatisfied(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange) bool { - roleAuths := types.RoleAuthorizationMap() + roleAuths := k.roleAuthorizationsForEntry(ctx, entry) for _, update := range change.RoleUpdates { roleAuth, ok := roleAuths[update.Role] if !ok { diff --git a/x/registry/keeper/query_server.go b/x/registry/keeper/query_server.go index ae72ef1a42..7a8b5d0dd6 100644 --- a/x/registry/keeper/query_server.go +++ b/x/registry/keeper/query_server.go @@ -132,3 +132,44 @@ func (qs QueryServer) PendingRoleChanges(ctx context.Context, req *types.QueryPe return &types.QueryPendingRoleChangesResponse{PendingRoleChanges: changes, Pagination: pageRes}, nil } + +// RegistryClass returns a single registry class (including its authorization policy) by id. +func (qs QueryServer) RegistryClass(ctx context.Context, req *types.QueryRegistryClassRequest) (*types.QueryRegistryClassResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + if err := req.Validate(); err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + class, err := qs.keeper.GetRegistryClass(sdkCtx, req.RegistryClassId) + if err != nil { + return nil, err + } + if class == nil { + return nil, types.NewErrCodeRegistryClassNotFound(req.RegistryClassId) + } + + return &types.QueryRegistryClassResponse{RegistryClass: *class}, nil +} + +// RegistryClasses returns all registry classes, paginated. +func (qs QueryServer) RegistryClasses(ctx context.Context, req *types.QueryRegistryClassesRequest) (*types.QueryRegistryClassesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + if err := req.Validate(); err != nil { + return nil, err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + classes, pageRes, err := qs.keeper.GetRegistryClasses(sdkCtx, req.Pagination) + if err != nil { + return nil, err + } + + return &types.QueryRegistryClassesResponse{RegistryClasses: classes, Pagination: pageRes}, nil +} diff --git a/x/registry/keeper/query_server_test.go b/x/registry/keeper/query_server_test.go index a614a0be40..ccaf30b696 100644 --- a/x/registry/keeper/query_server_test.go +++ b/x/registry/keeper/query_server_test.go @@ -20,7 +20,7 @@ func (s *KeeperTestSuite) TestQueryGetRegistry() { Addresses: []string{s.user1}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles, "") s.Require().NoError(err) queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) @@ -127,7 +127,7 @@ func (s *KeeperTestSuite) TestQueryGetRegistries() { } for _, key := range keys { - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles, "") s.Require().NoError(err) } @@ -248,7 +248,7 @@ func (s *KeeperTestSuite) TestQueryHasRole() { Addresses: []string{s.user1, s.user2}, }, } - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, key, roles, "") s.Require().NoError(err) queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) diff --git a/x/registry/keeper/registry_class_acceptance_test.go b/x/registry/keeper/registry_class_acceptance_test.go new file mode 100644 index 0000000000..df89248269 --- /dev/null +++ b/x/registry/keeper/registry_class_acceptance_test.go @@ -0,0 +1,308 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/x/nft" + nftkeeper "cosmossdk.io/x/nft/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// RegistryClassAcceptanceTestSuite covers Phase C1: registry classes provide maintainer-managed, +// asset-class-level authorization configuration that overrides the module's static default policy +// via two-tier resolution (class policy -> static default / NFT-owner fallback). +type RegistryClassAcceptanceTestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + registryKeeper keeper.Keeper + nftKeeper nftkeeper.Keeper + msgServer types.MsgServer + + nftClass nft.Class + + nftOwner string + maintainer string + controller string + stranger string + grantee string +} + +func TestRegistryClassAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(RegistryClassAcceptanceTestSuite)) +} + +func (s *RegistryClassAcceptanceTestSuite) SetupTest() { + s.app = app.Setup(s.T()) + s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + + s.nftKeeper = s.app.NFTKeeper + s.registryKeeper = s.app.RegistryKeeper + s.msgServer = keeper.NewMsgServer(s.registryKeeper) + + s.nftOwner = genAddr() + s.maintainer = genAddr() + s.controller = genAddr() + s.stranger = genAddr() + s.grantee = genAddr() + + s.nftClass = nft.Class{Id: "registry-class-test-nft-class"} + s.nftKeeper.SaveClass(s.ctx, s.nftClass) +} + +// mintNFT mints an NFT owned by s.nftOwner and returns its registry key. +func (s *RegistryClassAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { + n := nft.NFT{ClassId: s.nftClass.Id, Id: id} + ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) + s.Require().NoError(err) + s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) + return &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} +} + +// servicerRoleAuthorization builds a class policy for the SERVICER role that is satisfied by the +// current CONTROLLER's signature alone. This deviates from the static default (which only governs +// CONTROLLER and would otherwise fall back to NFT-owner authorization for SERVICER), letting us +// observe two-tier resolution. +func servicerRoleAuthorization() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{ + { + Description: "current controller may manage servicers", + Signatures: []*types.SignatureRequirement{ + { + Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + Roles: []*types.RoleAssignment{ + { + RoleSelector: &types.RoleAssignment_RegistryRole{ + RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + }, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }, + }, + }, + }, + }, + }, + } +} + +func (s *RegistryClassAcceptanceTestSuite) createClass(classID string, auths []types.RoleAuthorization) { + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: s.maintainer, + RegistryClassId: classID, + AssetClassId: s.nftClass.Id, + Maintainer: s.maintainer, + RoleAuthorizations: auths, + }) + s.Require().NoError(err) +} + +// TestCreateRegistryClass exercises class creation success and failure paths. +func (s *RegistryClassAcceptanceTestSuite) TestCreateRegistryClass() { + s.Run("success: signer is maintainer", func() { + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: s.maintainer, + RegistryClassId: "class-create-ok", + AssetClassId: s.nftClass.Id, + Maintainer: s.maintainer, + }) + s.Require().NoError(err) + + got, err := s.registryKeeper.GetRegistryClass(s.ctx, "class-create-ok") + s.Require().NoError(err) + s.Require().NotNil(got) + s.Require().Equal(s.maintainer, got.Maintainer) + s.Require().Equal(s.nftClass.Id, got.AssetClassId) + }) + + s.Run("reject: signer is not maintainer (ValidateBasic)", func() { + msg := &types.MsgCreateRegistryClass{ + Signer: s.stranger, + RegistryClassId: "class-bad-signer", + AssetClassId: s.nftClass.Id, + Maintainer: s.maintainer, + } + err := msg.ValidateBasic() + s.Require().Error(err) + s.Require().ErrorContains(err, "signer") + }) + + s.Run("reject: duplicate registry class id", func() { + s.createClass("class-dup", nil) + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: s.maintainer, + RegistryClassId: "class-dup", + AssetClassId: s.nftClass.Id, + Maintainer: s.maintainer, + }) + s.Require().Error(err) + s.Require().ErrorContains(err, "class-dup") + }) +} + +// TestUpdateRegistryClassRoleAuthorization exercises the maintainer-only update path. +func (s *RegistryClassAcceptanceTestSuite) TestUpdateRegistryClassRoleAuthorization() { + s.createClass("class-update", nil) + + s.Run("success: maintainer updates authorizations", func() { + _, err := s.msgServer.UpdateRegistryClassRoleAuthorization(s.ctx, &types.MsgUpdateRegistryClassRoleAuthorization{ + Signer: s.maintainer, + RegistryClassId: "class-update", + RoleAuthorizations: []types.RoleAuthorization{servicerRoleAuthorization()}, + }) + s.Require().NoError(err) + + got, err := s.registryKeeper.GetRegistryClass(s.ctx, "class-update") + s.Require().NoError(err) + s.Require().NotNil(got) + s.Require().Len(got.RoleAuthorizations, 1) + s.Require().Equal(types.RegistryRole_REGISTRY_ROLE_SERVICER, got.RoleAuthorizations[0].Role) + }) + + s.Run("reject: non-maintainer cannot update", func() { + _, err := s.msgServer.UpdateRegistryClassRoleAuthorization(s.ctx, &types.MsgUpdateRegistryClassRoleAuthorization{ + Signer: s.stranger, + RegistryClassId: "class-update", + }) + s.Require().Error(err) + s.Require().ErrorContains(err, "maintainer") + }) + + s.Run("reject: unknown registry class", func() { + _, err := s.msgServer.UpdateRegistryClassRoleAuthorization(s.ctx, &types.MsgUpdateRegistryClassRoleAuthorization{ + Signer: s.maintainer, + RegistryClassId: "does-not-exist", + }) + s.Require().Error(err) + s.Require().ErrorContains(err, "does-not-exist") + }) +} + +// TestRegisterNFTWithRegistryClass verifies that registering against a class requires the class to +// exist and persists the reference on the entry. +func (s *RegistryClassAcceptanceTestSuite) TestRegisterNFTWithRegistryClass() { + s.createClass("class-register", []types.RoleAuthorization{servicerRoleAuthorization()}) + + s.Run("reject: unknown registry class", func() { + key := s.mintNFT("nft-missing-class") + _, err := s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: key, + RegistryClassId: "no-such-class", + }) + s.Require().Error(err) + s.Require().ErrorContains(err, "no-such-class") + }) + + s.Run("success: entry stores registry class id", func() { + key := s.mintNFT("nft-with-class") + _, err := s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: key, + RegistryClassId: "class-register", + }) + s.Require().NoError(err) + + entry, err := s.registryKeeper.GetRegistry(s.ctx, key) + s.Require().NoError(err) + s.Require().NotNil(entry) + s.Require().Equal("class-register", entry.RegistryClassId) + }) +} + +// TestTwoTierResolution proves that an entry referencing a class is governed by the class policy, +// while an otherwise-identical entry with no class falls back to the static default (NFT-owner) for +// the same role. +func (s *RegistryClassAcceptanceTestSuite) TestTwoTierResolution() { + s.createClass("class-servicer", []types.RoleAuthorization{servicerRoleAuthorization()}) + + roles := []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.controller}}, + } + + s.Run("class policy: current controller may grant SERVICER", func() { + key := s.mintNFT("nft-classed") + _, err := s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: key, + Roles: roles, + RegistryClassId: "class-servicer", + }) + s.Require().NoError(err) + + // The controller (not the NFT owner) satisfies the class policy for SERVICER. + _, err = s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.controller, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.grantee}, + }) + s.Require().NoError(err) + + hasRole, err := s.registryKeeper.HasRole(s.ctx, key, types.RegistryRole_REGISTRY_ROLE_SERVICER, s.grantee) + s.Require().NoError(err) + s.Require().True(hasRole) + }) + + s.Run("legacy fallback: controller cannot grant SERVICER without a class", func() { + key := s.mintNFT("nft-unclassed") + _, err := s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: key, + Roles: roles, + }) + s.Require().NoError(err) + + // No class -> SERVICER is not in the static map -> NFT-owner authorization is required. + _, err = s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.controller, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.grantee}, + }) + s.Require().Error(err) + + // The NFT owner can grant under the legacy fallback. + _, err = s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.nftOwner, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.grantee}, + }) + s.Require().NoError(err) + }) +} + +// TestGenesisRoundTrip verifies registry classes survive an export/import cycle. +func (s *RegistryClassAcceptanceTestSuite) TestGenesisRoundTrip() { + s.createClass("class-genesis", []types.RoleAuthorization{servicerRoleAuthorization()}) + + exported := s.registryKeeper.ExportGenesis(s.ctx) + s.Require().NoError(exported.Validate()) + s.Require().Len(exported.RegistryClasses, 1) + s.Require().Equal("class-genesis", exported.RegistryClasses[0].RegistryClassId) + + // Re-initialize into a fresh app and confirm the class is present. + freshApp := app.Setup(s.T()) + freshCtx := freshApp.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + freshApp.RegistryKeeper.InitGenesis(freshCtx, exported) + + got, err := freshApp.RegistryKeeper.GetRegistryClass(freshCtx, "class-genesis") + s.Require().NoError(err) + s.Require().NotNil(got) + s.Require().Equal(s.maintainer, got.Maintainer) + s.Require().Len(got.RoleAuthorizations, 1) +} diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index ebc465b284..d332ca4557 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -87,7 +87,7 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) mintNFT(id string) *types.Re } func (s *RoleChangeAccumulationAcceptanceTestSuite) setupRegistry(key *types.RegistryKey, roles []types.RolesEntry) { - s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles)) + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles, "")) } // proposeNewController opens a pending GRANT of the CONTROLLER role to newController, signed by diff --git a/x/registry/types/authorization.go b/x/registry/types/authorization.go index a91e50eeee..90e65018d3 100644 --- a/x/registry/types/authorization.go +++ b/x/registry/types/authorization.go @@ -11,7 +11,12 @@ func DefaultRoleAuthorizations() []RoleAuthorization { // RoleAuthorizationMap returns a map of RegistryRole → RoleAuthorization for fast lookup. func RoleAuthorizationMap() map[RegistryRole]RoleAuthorization { - auths := DefaultRoleAuthorizations() + return RoleAuthorizationMapFrom(DefaultRoleAuthorizations()) +} + +// RoleAuthorizationMapFrom builds a map of RegistryRole → RoleAuthorization from the given slice. +// The last entry wins if a role appears more than once. +func RoleAuthorizationMapFrom(auths []RoleAuthorization) map[RegistryRole]RoleAuthorization { m := make(map[RegistryRole]RoleAuthorization, len(auths)) for _, a := range auths { m[a.Role] = a diff --git a/x/registry/types/authorization.pb.go b/x/registry/types/authorization.pb.go index a62fb8bae0..ce3c8b4218 100644 --- a/x/registry/types/authorization.pb.go +++ b/x/registry/types/authorization.pb.go @@ -5,6 +5,8 @@ package types import ( fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -73,7 +75,8 @@ type NftRole int32 const ( // NFT_ROLE_UNSPECIFIED is the default/unset value. NftRole_NFT_ROLE_UNSPECIFIED NftRole = 0 - // NFT_ROLE_NFT_OWNER is the owner of the NFT (value owner from the metadata module Scope). + // NFT_ROLE_NFT_OWNER is the owner of the NFT. + // For metadata-scope NFTs, this is the scope value owner; otherwise it's the owner from the x/nft module. NftRole_NFT_ROLE_NFT_OWNER NftRole = 1 ) @@ -151,6 +154,84 @@ func (Assignment) EnumDescriptor() ([]byte, []int) { return fileDescriptor_3c5d2a6c632d9975, []int{2} } +// RegistryClass provides asset class-level authorization over role updates. +// It defines authorization rules for an entire NFT collection (asset class), allowing the +// maintainer to establish custom role update policies without requiring governance proposals. +type RegistryClass struct { + // registry_class_id is the unique identifier for this registry class (e.g. "dart-loan-v1"). + // It is required since NFT classes do not have an inherent owner, and must be unique across + // all registry classes. + RegistryClassId string `protobuf:"bytes,1,opt,name=registry_class_id,json=registryClassId,proto3" json:"registryClassId,omitempty"` + // asset_class_id is the Scope Specification ID or NFT Class ID this registry class governs. + AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` + // maintainer is the address authorized to manage this registry class. It is permanent and + // cannot be changed once set. + Maintainer string `protobuf:"bytes,3,opt,name=maintainer,proto3" json:"maintainer,omitempty"` + // role_authorizations are the authorization rules for role updates within this asset class. + RoleAuthorizations []RoleAuthorization `protobuf:"bytes,4,rep,name=role_authorizations,json=roleAuthorizations,proto3" json:"role_authorizations"` +} + +func (m *RegistryClass) Reset() { *m = RegistryClass{} } +func (m *RegistryClass) String() string { return proto.CompactTextString(m) } +func (*RegistryClass) ProtoMessage() {} +func (*RegistryClass) Descriptor() ([]byte, []int) { + return fileDescriptor_3c5d2a6c632d9975, []int{0} +} +func (m *RegistryClass) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RegistryClass) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RegistryClass.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RegistryClass) XXX_Merge(src proto.Message) { + xxx_messageInfo_RegistryClass.Merge(m, src) +} +func (m *RegistryClass) XXX_Size() int { + return m.Size() +} +func (m *RegistryClass) XXX_DiscardUnknown() { + xxx_messageInfo_RegistryClass.DiscardUnknown(m) +} + +var xxx_messageInfo_RegistryClass proto.InternalMessageInfo + +func (m *RegistryClass) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +func (m *RegistryClass) GetAssetClassId() string { + if m != nil { + return m.AssetClassId + } + return "" +} + +func (m *RegistryClass) GetMaintainer() string { + if m != nil { + return m.Maintainer + } + return "" +} + +func (m *RegistryClass) GetRoleAuthorizations() []RoleAuthorization { + if m != nil { + return m.RoleAuthorizations + } + return nil +} + // RoleAuthorization configures who must sign to update a specific role. // Contains one or more Authorization paths; the update is approved if ANY path is fully satisfied. type RoleAuthorization struct { @@ -165,7 +246,7 @@ func (m *RoleAuthorization) Reset() { *m = RoleAuthorization{} } func (m *RoleAuthorization) String() string { return proto.CompactTextString(m) } func (*RoleAuthorization) ProtoMessage() {} func (*RoleAuthorization) Descriptor() ([]byte, []int) { - return fileDescriptor_3c5d2a6c632d9975, []int{0} + return fileDescriptor_3c5d2a6c632d9975, []int{1} } func (m *RoleAuthorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -221,7 +302,7 @@ func (m *Authorization) Reset() { *m = Authorization{} } func (m *Authorization) String() string { return proto.CompactTextString(m) } func (*Authorization) ProtoMessage() {} func (*Authorization) Descriptor() ([]byte, []int) { - return fileDescriptor_3c5d2a6c632d9975, []int{1} + return fileDescriptor_3c5d2a6c632d9975, []int{2} } func (m *Authorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -276,7 +357,7 @@ func (m *SignatureRequirement) Reset() { *m = SignatureRequirement{} } func (m *SignatureRequirement) String() string { return proto.CompactTextString(m) } func (*SignatureRequirement) ProtoMessage() {} func (*SignatureRequirement) Descriptor() ([]byte, []int) { - return fileDescriptor_3c5d2a6c632d9975, []int{2} + return fileDescriptor_3c5d2a6c632d9975, []int{3} } func (m *SignatureRequirement) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -324,6 +405,7 @@ type RoleAssignment struct { // role_selector identifies the role to resolve, either a single role or a priority list. // // Types that are valid to be assigned to RoleSelector: + // // *RoleAssignment_RegistryRole // *RoleAssignment_NftRole // *RoleAssignment_RolePriority @@ -336,7 +418,7 @@ func (m *RoleAssignment) Reset() { *m = RoleAssignment{} } func (m *RoleAssignment) String() string { return proto.CompactTextString(m) } func (*RoleAssignment) ProtoMessage() {} func (*RoleAssignment) Descriptor() ([]byte, []int) { - return fileDescriptor_3c5d2a6c632d9975, []int{3} + return fileDescriptor_3c5d2a6c632d9975, []int{4} } func (m *RoleAssignment) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -440,7 +522,7 @@ func (m *RolePriority) Reset() { *m = RolePriority{} } func (m *RolePriority) String() string { return proto.CompactTextString(m) } func (*RolePriority) ProtoMessage() {} func (*RolePriority) Descriptor() ([]byte, []int) { - return fileDescriptor_3c5d2a6c632d9975, []int{4} + return fileDescriptor_3c5d2a6c632d9975, []int{5} } func (m *RolePriority) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -491,7 +573,7 @@ func (m *RolePriorityEntry) Reset() { *m = RolePriorityEntry{} } func (m *RolePriorityEntry) String() string { return proto.CompactTextString(m) } func (*RolePriorityEntry) ProtoMessage() {} func (*RolePriorityEntry) Descriptor() ([]byte, []int) { - return fileDescriptor_3c5d2a6c632d9975, []int{5} + return fileDescriptor_3c5d2a6c632d9975, []int{6} } func (m *RolePriorityEntry) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -569,6 +651,7 @@ func init() { proto.RegisterEnum("provenance.registry.v1.SignatureType", SignatureType_name, SignatureType_value) proto.RegisterEnum("provenance.registry.v1.NftRole", NftRole_name, NftRole_value) proto.RegisterEnum("provenance.registry.v1.Assignment", Assignment_name, Assignment_value) + proto.RegisterType((*RegistryClass)(nil), "provenance.registry.v1.RegistryClass") proto.RegisterType((*RoleAuthorization)(nil), "provenance.registry.v1.RoleAuthorization") proto.RegisterType((*Authorization)(nil), "provenance.registry.v1.Authorization") proto.RegisterType((*SignatureRequirement)(nil), "provenance.registry.v1.SignatureRequirement") @@ -582,48 +665,115 @@ func init() { } var fileDescriptor_3c5d2a6c632d9975 = []byte{ - // 648 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x55, 0xcf, 0x4e, 0x13, 0x41, - 0x18, 0xef, 0x94, 0x02, 0xfa, 0x01, 0x75, 0x9d, 0x10, 0x52, 0x31, 0x59, 0x9a, 0x8d, 0x10, 0x6c, - 0xb4, 0x0d, 0xe8, 0x41, 0x23, 0x97, 0x16, 0x06, 0x6d, 0x2c, 0x4b, 0x9d, 0x6e, 0x43, 0xf0, 0xb2, - 0x29, 0x75, 0x80, 0x8d, 0x65, 0xa7, 0xce, 0x4e, 0x89, 0xf5, 0xe2, 0x23, 0xe8, 0x03, 0x78, 0xf6, - 0x29, 0xf4, 0xe0, 0xcd, 0x23, 0x47, 0x8f, 0x06, 0x5e, 0xc4, 0xec, 0x74, 0x97, 0xee, 0x56, 0xb6, - 0xea, 0xcd, 0xdb, 0xcc, 0x7c, 0xbf, 0x7f, 0xf3, 0xcd, 0xcc, 0x2e, 0x14, 0xba, 0x82, 0x9f, 0x32, - 0xb7, 0xe5, 0xb6, 0x59, 0x49, 0xb0, 0x23, 0xc7, 0x93, 0xa2, 0x5f, 0x3a, 0x5d, 0x2b, 0xb5, 0x7a, - 0xf2, 0x98, 0x0b, 0xe7, 0x5d, 0x4b, 0x3a, 0xdc, 0x2d, 0x76, 0x05, 0x97, 0x1c, 0x2f, 0x0c, 0xb1, - 0xc5, 0x10, 0x5b, 0x3c, 0x5d, 0x5b, 0x5c, 0x4e, 0xd0, 0xb8, 0xc4, 0x28, 0xba, 0xf1, 0x09, 0xc1, - 0x4d, 0xca, 0x3b, 0xac, 0x1c, 0x95, 0xc6, 0x8f, 0x20, 0x23, 0x78, 0x87, 0xe5, 0x50, 0x1e, 0xad, - 0x66, 0xd7, 0xef, 0x14, 0xaf, 0xf6, 0x28, 0xd2, 0x60, 0xec, 0x0b, 0x50, 0xc5, 0xc0, 0x3b, 0x90, - 0x8d, 0xa5, 0xf4, 0x72, 0xe9, 0xfc, 0xc4, 0xea, 0xcc, 0xfa, 0x72, 0x92, 0x46, 0xcc, 0x98, 0x8e, - 0x90, 0x8d, 0xf7, 0x30, 0x17, 0x4f, 0x96, 0x87, 0x99, 0x57, 0xcc, 0x6b, 0x0b, 0xa7, 0xeb, 0x4f, - 0x55, 0xc0, 0xeb, 0x34, 0xba, 0x84, 0x6b, 0x00, 0x9e, 0x73, 0xe4, 0xb6, 0x64, 0x4f, 0xb0, 0xd0, - 0xfd, 0x5e, 0x92, 0x7b, 0x23, 0x44, 0x52, 0xf6, 0xa6, 0xe7, 0x08, 0x76, 0xc2, 0x5c, 0x49, 0x23, - 0x7c, 0xe3, 0x03, 0x82, 0xf9, 0xab, 0x40, 0xf8, 0x31, 0x64, 0x64, 0xbf, 0x1b, 0xb6, 0x68, 0xf9, - 0x8f, 0x06, 0x56, 0xbf, 0xcb, 0xa8, 0xa2, 0xe0, 0x0d, 0x98, 0xf4, 0x7b, 0x15, 0x86, 0x5b, 0x49, - 0x6c, 0xaf, 0x7f, 0x2e, 0x9e, 0x1f, 0x46, 0xc5, 0x1a, 0x90, 0x8c, 0xaf, 0x69, 0xc8, 0xc6, 0x2b, - 0xf8, 0x39, 0xcc, 0x85, 0x3c, 0xfb, 0x5f, 0xcf, 0xed, 0x59, 0x8a, 0xce, 0x8a, 0xc8, 0x1c, 0x6f, - 0xc0, 0x35, 0xf7, 0x50, 0x0e, 0x74, 0xd2, 0x4a, 0x67, 0x29, 0x49, 0xc7, 0x3c, 0x94, 0x81, 0xc4, - 0xb4, 0x3b, 0x18, 0xaa, 0x28, 0xbc, 0xc3, 0xec, 0xae, 0x70, 0xb8, 0x70, 0x64, 0x3f, 0x37, 0x91, - 0x47, 0xab, 0x33, 0x63, 0xa2, 0xf0, 0x0e, 0xab, 0x07, 0x58, 0x15, 0x25, 0x32, 0xc7, 0x15, 0x80, - 0xd6, 0xe5, 0x2e, 0x73, 0x19, 0x15, 0xc6, 0x48, 0xbc, 0x48, 0xc3, 0x4e, 0x45, 0x58, 0x95, 0x1b, - 0x41, 0x20, 0x8f, 0x75, 0x58, 0x5b, 0x72, 0x61, 0x34, 0x60, 0x36, 0x6a, 0x8a, 0x37, 0x61, 0x9a, - 0xb9, 0x52, 0x38, 0xcc, 0xcb, 0x21, 0x75, 0x1e, 0x77, 0xff, 0x26, 0x2b, 0x71, 0xfd, 0x7e, 0x85, - 0x4c, 0xe3, 0x73, 0xf0, 0x8c, 0x62, 0xe5, 0xff, 0xe8, 0x5c, 0x2a, 0x53, 0x83, 0x17, 0x5d, 0xf8, - 0x86, 0x60, 0x2e, 0x76, 0x27, 0xb1, 0x0e, 0x8b, 0x8d, 0xea, 0x53, 0xb3, 0x6c, 0x35, 0x29, 0xb1, - 0xad, 0xfd, 0x3a, 0xb1, 0x9b, 0x66, 0xa3, 0x4e, 0x36, 0xab, 0xdb, 0x55, 0xb2, 0xa5, 0xa5, 0xf0, - 0x12, 0xdc, 0x1e, 0xa9, 0x53, 0xf2, 0xa2, 0x59, 0xa5, 0x64, 0xcb, 0x2e, 0xd7, 0x6a, 0x1a, 0xc2, - 0x2b, 0x60, 0x8c, 0x01, 0xd8, 0xd5, 0x6d, 0xbb, 0x41, 0x2c, 0x2d, 0x3d, 0x56, 0xc8, 0xdc, 0xd7, - 0x26, 0xc6, 0x0a, 0x99, 0xfb, 0xa1, 0x50, 0xa6, 0xf0, 0x04, 0xa6, 0x83, 0x1d, 0xe2, 0x1c, 0xcc, - 0x9b, 0xdb, 0x96, 0x4d, 0x77, 0x6b, 0xa3, 0xb1, 0x17, 0x00, 0x5f, 0x56, 0xfc, 0xc1, 0xee, 0x9e, - 0x49, 0xa8, 0x86, 0x0a, 0x5f, 0x10, 0x40, 0xe4, 0xe9, 0x2c, 0xc2, 0x42, 0xb9, 0xe1, 0xbb, 0xee, - 0x10, 0xd3, 0xfa, 0x5d, 0x22, 0x52, 0xdb, 0x6c, 0x52, 0x4a, 0x4c, 0x4b, 0x43, 0x23, 0x9c, 0x60, - 0x5d, 0x35, 0x23, 0x9d, 0x54, 0x53, 0xfb, 0xc3, 0x90, 0x8d, 0xd4, 0x4c, 0xb2, 0xa7, 0x65, 0x46, - 0x3c, 0x4c, 0xb2, 0xa7, 0xb0, 0x93, 0x57, 0xad, 0xd7, 0x6a, 0xda, 0x54, 0xe5, 0xf5, 0xf7, 0x73, - 0x1d, 0x9d, 0x9d, 0xeb, 0xe8, 0xe7, 0xb9, 0x8e, 0x3e, 0x5e, 0xe8, 0xa9, 0xb3, 0x0b, 0x3d, 0xf5, - 0xe3, 0x42, 0x4f, 0xc1, 0x2d, 0x87, 0x27, 0xdc, 0x87, 0x3a, 0x7a, 0xf9, 0xf0, 0xc8, 0x91, 0xc7, - 0xbd, 0x83, 0x62, 0x9b, 0x9f, 0x94, 0x86, 0xa0, 0xfb, 0x0e, 0x8f, 0xcc, 0x4a, 0x6f, 0x87, 0x3f, - 0x0a, 0xff, 0x3b, 0xe5, 0x1d, 0x4c, 0xa9, 0x7f, 0xc4, 0x83, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, - 0x56, 0x21, 0xec, 0x03, 0x90, 0x06, 0x00, 0x00, + // 797 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x55, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xf6, 0x3a, 0x6e, 0x02, 0x2f, 0xb1, 0xbb, 0x1d, 0xac, 0xc8, 0x31, 0xc2, 0x8e, 0x56, 0x4d, + 0x55, 0xac, 0xd6, 0x56, 0x0b, 0x87, 0x22, 0x7a, 0xb1, 0xdd, 0x0d, 0x58, 0xb8, 0x5b, 0x33, 0xb6, + 0x15, 0x85, 0xcb, 0xb2, 0xb5, 0xa7, 0xee, 0x08, 0x7b, 0xc7, 0xcc, 0x8c, 0x23, 0xcc, 0x85, 0x9f, + 0x00, 0x3f, 0x20, 0x67, 0x7e, 0x01, 0x47, 0x38, 0x70, 0xcb, 0x31, 0xe2, 0xc4, 0x29, 0x42, 0xc9, + 0x8d, 0x5f, 0x81, 0x76, 0x76, 0xd7, 0x5e, 0x6f, 0xb2, 0x8e, 0xb8, 0xf5, 0x36, 0xf3, 0xbe, 0xef, + 0x7d, 0xef, 0x9b, 0x37, 0x6f, 0x76, 0xa1, 0x32, 0xe5, 0xec, 0x84, 0xb8, 0x8e, 0x3b, 0x20, 0x35, + 0x4e, 0x46, 0x54, 0x48, 0x3e, 0xaf, 0x9d, 0x3c, 0xa9, 0x39, 0x33, 0xf9, 0x96, 0x71, 0xfa, 0xa3, + 0x23, 0x29, 0x73, 0xab, 0x53, 0xce, 0x24, 0x43, 0xbb, 0x4b, 0x6e, 0x35, 0xe4, 0x56, 0x4f, 0x9e, + 0x14, 0xf3, 0x23, 0x36, 0x62, 0x8a, 0x52, 0xf3, 0x56, 0x3e, 0xbb, 0xb8, 0x37, 0x60, 0x62, 0xc2, + 0x84, 0xed, 0x03, 0xfe, 0x26, 0x80, 0x0e, 0x12, 0x8a, 0x2e, 0x44, 0x15, 0xcd, 0x38, 0x4d, 0x43, + 0x16, 0x07, 0xa1, 0xe6, 0xd8, 0x11, 0x02, 0xb5, 0xe0, 0x5e, 0xc8, 0xb1, 0x07, 0x5e, 0xc4, 0xa6, + 0xc3, 0x82, 0xb6, 0xaf, 0x3d, 0x7c, 0xbf, 0xf1, 0xd1, 0xbf, 0x17, 0xe5, 0x3d, 0x1e, 0x65, 0xb7, + 0x86, 0x8f, 0xd8, 0x84, 0x4a, 0x32, 0x99, 0xca, 0x39, 0xbe, 0x1b, 0x83, 0xd0, 0x7d, 0xc8, 0x39, + 0x42, 0x10, 0xb9, 0xd4, 0x49, 0x7b, 0x3a, 0x78, 0x47, 0x45, 0x43, 0xd6, 0x33, 0x80, 0x89, 0x43, + 0x5d, 0xe9, 0x50, 0x97, 0xf0, 0xc2, 0x86, 0xaa, 0x54, 0xf8, 0xeb, 0xb7, 0xc7, 0xf9, 0xe0, 0x3c, + 0xf5, 0xe1, 0x90, 0x13, 0x21, 0xba, 0x92, 0x53, 0x77, 0x84, 0x23, 0x5c, 0xf4, 0x2d, 0x7c, 0xc0, + 0xd9, 0x98, 0xd8, 0x2b, 0x8d, 0x14, 0x85, 0xcc, 0xfe, 0xc6, 0xc3, 0xed, 0xa7, 0x1f, 0x57, 0x6f, + 0x6e, 0x65, 0x15, 0xb3, 0x31, 0xa9, 0x47, 0x33, 0x1a, 0x99, 0xb3, 0x8b, 0x72, 0x0a, 0x23, 0x1e, + 0x07, 0x84, 0x71, 0xaa, 0xc1, 0xbd, 0x6b, 0x7c, 0xf4, 0x0c, 0x32, 0x1e, 0x57, 0x75, 0x25, 0xf7, + 0xf4, 0x7e, 0x62, 0xa1, 0x60, 0xed, 0x09, 0x60, 0x95, 0x81, 0x5e, 0x42, 0x2e, 0x66, 0x36, 0xad, + 0xcc, 0x1e, 0x24, 0x69, 0xac, 0x14, 0xc6, 0xb1, 0x64, 0xe3, 0x27, 0xc8, 0xae, 0x3a, 0xdb, 0x87, + 0xed, 0x21, 0x11, 0x03, 0x4e, 0xa7, 0xde, 0xd6, 0xbf, 0x36, 0x1c, 0x0d, 0xa1, 0x36, 0x80, 0xa0, + 0x23, 0xd7, 0x91, 0x33, 0x4e, 0xc2, 0xea, 0x8f, 0x92, 0xaa, 0x77, 0x43, 0x26, 0x26, 0xdf, 0xcf, + 0x28, 0x27, 0x13, 0xe2, 0x4a, 0x1c, 0xc9, 0x37, 0x7e, 0xd6, 0x20, 0x7f, 0x13, 0x09, 0x7d, 0x06, + 0x19, 0x39, 0x9f, 0x86, 0x2d, 0x3a, 0xb8, 0xb5, 0x40, 0x6f, 0x3e, 0x25, 0x58, 0xa5, 0xa0, 0xe7, + 0x70, 0xc7, 0xeb, 0x55, 0x68, 0xee, 0xc1, 0xda, 0x7b, 0x14, 0x9e, 0x19, 0x65, 0xcb, 0x4f, 0x32, + 0xfe, 0x48, 0x43, 0x6e, 0x15, 0x41, 0x5f, 0x41, 0x76, 0x31, 0xd1, 0xff, 0xf7, 0xde, 0xbe, 0x4c, + 0xe1, 0x1d, 0x1e, 0xd9, 0xa3, 0xe7, 0xf0, 0x9e, 0xfb, 0x46, 0xfa, 0x3a, 0x69, 0xa5, 0x53, 0x4e, + 0xd2, 0xb1, 0xde, 0xc8, 0x40, 0x62, 0xcb, 0xf5, 0x97, 0xca, 0x8a, 0x37, 0xb1, 0x53, 0x4e, 0x19, + 0xa7, 0x72, 0xae, 0xc6, 0x7d, 0x7b, 0x8d, 0x15, 0x36, 0x26, 0x9d, 0x80, 0xab, 0xac, 0x44, 0xf6, + 0xa8, 0x01, 0xe0, 0x2c, 0x4e, 0x59, 0xc8, 0x28, 0x33, 0x46, 0xe2, 0x20, 0x2d, 0x3b, 0x15, 0xc9, + 0x6a, 0xdc, 0x0d, 0x0c, 0x09, 0x32, 0x26, 0x03, 0xc9, 0xb8, 0xd1, 0x85, 0x9d, 0x68, 0x51, 0xd4, + 0x84, 0x2d, 0xe2, 0x4a, 0x4e, 0x89, 0x28, 0x68, 0xb7, 0xbf, 0xab, 0x30, 0xcd, 0x74, 0xbd, 0x7e, + 0x85, 0x99, 0xc6, 0xaf, 0xc1, 0x33, 0x5a, 0x81, 0xdf, 0xa1, 0x7b, 0x69, 0x6c, 0xfa, 0x2f, 0xba, + 0xf2, 0xa7, 0x06, 0xd9, 0x95, 0x99, 0x44, 0x25, 0x28, 0x76, 0x5b, 0x5f, 0x58, 0xf5, 0x5e, 0x1f, + 0x9b, 0x76, 0xef, 0xb8, 0x63, 0xda, 0x7d, 0xab, 0xdb, 0x31, 0x9b, 0xad, 0xc3, 0x96, 0xf9, 0x42, + 0x4f, 0xa1, 0x32, 0x7c, 0x18, 0xc3, 0xb1, 0xf9, 0x75, 0xbf, 0x85, 0xcd, 0x17, 0x76, 0xbd, 0xdd, + 0xd6, 0x35, 0xf4, 0x00, 0x8c, 0x35, 0x04, 0xbb, 0x75, 0x68, 0x77, 0xcd, 0x9e, 0x9e, 0x5e, 0x2b, + 0x64, 0x1d, 0xeb, 0x1b, 0x6b, 0x85, 0xac, 0xe3, 0x50, 0x28, 0x53, 0xf9, 0x1c, 0xb6, 0x82, 0x13, + 0xa2, 0x02, 0xe4, 0xad, 0xc3, 0x9e, 0x8d, 0x5f, 0xb5, 0xe3, 0xb6, 0x77, 0x01, 0x2d, 0x10, 0x6f, + 0xf1, 0xea, 0xc8, 0x32, 0xb1, 0xae, 0x55, 0x7e, 0xd7, 0x00, 0x22, 0x4f, 0xa7, 0x08, 0xbb, 0xf5, + 0xae, 0x57, 0xf5, 0xa5, 0x69, 0xf5, 0xae, 0x4b, 0x44, 0xb0, 0x66, 0x1f, 0x63, 0xd3, 0xea, 0xe9, + 0x5a, 0x2c, 0x27, 0x88, 0xab, 0x66, 0xa4, 0x93, 0x30, 0x75, 0x3e, 0x04, 0xb9, 0x08, 0x66, 0x99, + 0x47, 0x7a, 0x26, 0x56, 0xc3, 0x32, 0x8f, 0x14, 0xf7, 0xce, 0x4d, 0xf1, 0x76, 0x5b, 0xdf, 0x6c, + 0x7c, 0x77, 0x76, 0x59, 0xd2, 0xce, 0x2f, 0x4b, 0xda, 0x3f, 0x97, 0x25, 0xed, 0x97, 0xab, 0x52, + 0xea, 0xfc, 0xaa, 0x94, 0xfa, 0xfb, 0xaa, 0x94, 0x82, 0x3d, 0xca, 0x12, 0xe6, 0xa1, 0xa3, 0x7d, + 0xf3, 0xe9, 0x88, 0xca, 0xb7, 0xb3, 0xd7, 0xd5, 0x01, 0x9b, 0xd4, 0x96, 0xa4, 0xc7, 0x94, 0x45, + 0x76, 0xb5, 0x1f, 0x96, 0xff, 0x51, 0xef, 0x3b, 0x25, 0x5e, 0x6f, 0xaa, 0x5f, 0xe8, 0x27, 0xff, + 0x05, 0x00, 0x00, 0xff, 0xff, 0xe2, 0xc8, 0x9d, 0x42, 0xe0, 0x07, 0x00, 0x00, +} + +func (m *RegistryClass) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RegistryClass) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RegistryClass) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RoleAuthorizations) > 0 { + for iNdEx := len(m.RoleAuthorizations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleAuthorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintAuthorization(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.Maintainer) > 0 { + i -= len(m.Maintainer) + copy(dAtA[i:], m.Maintainer) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.Maintainer))) + i-- + dAtA[i] = 0x1a + } + if len(m.AssetClassId) > 0 { + i -= len(m.AssetClassId) + copy(dAtA[i:], m.AssetClassId) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.AssetClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } func (m *RoleAuthorization) Marshal() (dAtA []byte, err error) { @@ -940,6 +1090,33 @@ func encodeVarintAuthorization(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } +func (m *RegistryClass) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovAuthorization(uint64(l)) + } + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovAuthorization(uint64(l)) + } + l = len(m.Maintainer) + if l > 0 { + n += 1 + l + sovAuthorization(uint64(l)) + } + if len(m.RoleAuthorizations) > 0 { + for _, e := range m.RoleAuthorizations { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + return n +} + func (m *RoleAuthorization) Size() (n int) { if m == nil { return 0 @@ -1092,6 +1269,186 @@ func sovAuthorization(x uint64) (n int) { func sozAuthorization(x uint64) (n int) { return sovAuthorization(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *RegistryClass) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RegistryClass: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RegistryClass: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Maintainer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Maintainer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleAuthorizations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthAuthorization + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthAuthorization + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleAuthorizations = append(m.RoleAuthorizations, RoleAuthorization{}) + if err := m.RoleAuthorizations[len(m.RoleAuthorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipAuthorization(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthAuthorization + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *RoleAuthorization) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/class.go b/x/registry/types/class.go new file mode 100644 index 0000000000..26ef558eed --- /dev/null +++ b/x/registry/types/class.go @@ -0,0 +1,67 @@ +package types + +import ( + "errors" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// MaxLenRegistryClassID is the maximum length of a registry class id. +const MaxLenRegistryClassID = 128 + +// Validate validates the RegistryClass. +func (m *RegistryClass) Validate() error { + if m == nil { + return fmt.Errorf("registry class cannot be nil") + } + + var errs []error + if err := ValidateRegistryClassID(m.RegistryClassId); err != nil { + errs = append(errs, fmt.Errorf("registry_class_id: %w", err)) + } + + if err := ValidateClassID(m.AssetClassId); err != nil { + errs = append(errs, fmt.Errorf("asset_class_id: %w", err)) + } + + if _, err := sdk.AccAddressFromBech32(m.Maintainer); err != nil { + errs = append(errs, fmt.Errorf("maintainer: %w", err)) + } + + errs = append(errs, validateRoleAuthorizations(m.RoleAuthorizations)...) + + return errors.Join(errs...) +} + +// ValidateRegistryClassID validates the registry class id format. +func ValidateRegistryClassID(registryClassID string) error { + if err := ValidateStringLength(registryClassID, 1, MaxLenRegistryClassID); err != nil { + return err + } + if !alNumDashRx.MatchString(registryClassID) { + return fmt.Errorf("%q must only contain alphanumeric, '-', '.' characters", registryClassID) + } + return nil +} + +// validateRoleAuthorizations validates a set of role authorization policies. Each policy must +// reference a known, specified role, and a role may be configured at most once. +func validateRoleAuthorizations(auths []RoleAuthorization) []error { + var errs []error + seen := make(map[RegistryRole]bool, len(auths)) + for i, auth := range auths { + if err := auth.Role.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: role %s", i, err)) + } else if seen[auth.Role] { + errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: duplicate role %s", i, auth.Role.ShortString())) + } else { + seen[auth.Role] = true + } + + if len(auth.Authorizations) == 0 { + errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: at least one authorization path is required", i)) + } + } + return errs +} diff --git a/x/registry/types/errors.go b/x/registry/types/errors.go index fb79880f44..8b4a22ae5f 100644 --- a/x/registry/types/errors.go +++ b/x/registry/types/errors.go @@ -39,6 +39,8 @@ const ( ErrCodeAddressDoesNotHaveRole ErrCode = "ADDRESS_DOES_NOT_HAVE_ROLE" ErrCodeInvalidField ErrCode = "INVALID_FIELD" ErrCodePendingChangeNotFound ErrCode = "PENDING_CHANGE_NOT_FOUND" + ErrCodeRegistryClassExists ErrCode = "REGISTRY_CLASS_ALREADY_EXISTS" + ErrCodeRegistryClassNotFound ErrCode = "REGISTRY_CLASS_NOT_FOUND" ) var ( @@ -52,6 +54,8 @@ var ( ErrAddressDoesNotHaveRole = cerrs.Register(ModuleName, 8, "address does not have role") ErrInvalidField = cerrs.Register(ModuleName, 9, "invalid field") ErrPendingChangeNotFound = cerrs.Register(ModuleName, 10, "pending role change not found") + ErrRegistryClassExists = cerrs.Register(ModuleName, 11, "registry class already exists") + ErrRegistryClassNotFound = cerrs.Register(ModuleName, 12, "registry class not found") ) func NewErrCodeRegistryAlreadyExists(key string) error { @@ -89,3 +93,11 @@ func NewErrCodeInvalidField(field, format string, args ...interface{}) error { func NewErrCodePendingChangeNotFound(changeID string) error { return cerrs.Wrapf(ErrPendingChangeNotFound, "pending role change not found: %q", changeID) } + +func NewErrCodeRegistryClassExists(registryClassID string) error { + return cerrs.Wrapf(ErrRegistryClassExists, "registry class already exists: %q", registryClassID) +} + +func NewErrCodeRegistryClassNotFound(registryClassID string) error { + return cerrs.Wrapf(ErrRegistryClassNotFound, "registry class not found: %q", registryClassID) +} diff --git a/x/registry/types/events.go b/x/registry/types/events.go index 6a1f4d489d..a8509b0893 100644 --- a/x/registry/types/events.go +++ b/x/registry/types/events.go @@ -76,6 +76,23 @@ func NewEventRoleChangeApplied(change *PendingRoleChange) *EventRoleChangeApplie } } +// NewEventRegistryClassCreated returns a new EventRegistryClassCreated. +func NewEventRegistryClassCreated(class *RegistryClass) *EventRegistryClassCreated { + return &EventRegistryClassCreated{ + RegistryClassId: class.RegistryClassId, + AssetClassId: class.AssetClassId, + Maintainer: class.Maintainer, + } +} + +// NewEventRegistryClassUpdated returns a new EventRegistryClassUpdated. +func NewEventRegistryClassUpdated(class *RegistryClass) *EventRegistryClassUpdated { + return &EventRegistryClassUpdated{ + RegistryClassId: class.RegistryClassId, + Maintainer: class.Maintainer, + } +} + // GetChangeEvents gets all the events that represent the changes from oldReg to newReg. // Panics if they have different keys (unless oldReg or newReg is nil). func GetChangeEvents(oldReg, newReg *RegistryEntry) ([]*EventRoleGranted, []*EventRoleRevoked) { diff --git a/x/registry/types/events.pb.go b/x/registry/types/events.pb.go index 0503d0c4c5..b8ac8e3f0e 100644 --- a/x/registry/types/events.pb.go +++ b/x/registry/types/events.pb.go @@ -502,6 +502,120 @@ func (m *EventRoleChangeApplied) GetChangeId() string { return "" } +// EventRegistryClassCreated is emitted when a registry class is created. +type EventRegistryClassCreated struct { + RegistryClassId string `protobuf:"bytes,1,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` + AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` + Maintainer string `protobuf:"bytes,3,opt,name=maintainer,proto3" json:"maintainer,omitempty"` +} + +func (m *EventRegistryClassCreated) Reset() { *m = EventRegistryClassCreated{} } +func (m *EventRegistryClassCreated) String() string { return proto.CompactTextString(m) } +func (*EventRegistryClassCreated) ProtoMessage() {} +func (*EventRegistryClassCreated) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{8} +} +func (m *EventRegistryClassCreated) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRegistryClassCreated) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRegistryClassCreated.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRegistryClassCreated) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRegistryClassCreated.Merge(m, src) +} +func (m *EventRegistryClassCreated) XXX_Size() int { + return m.Size() +} +func (m *EventRegistryClassCreated) XXX_DiscardUnknown() { + xxx_messageInfo_EventRegistryClassCreated.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRegistryClassCreated proto.InternalMessageInfo + +func (m *EventRegistryClassCreated) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +func (m *EventRegistryClassCreated) GetAssetClassId() string { + if m != nil { + return m.AssetClassId + } + return "" +} + +func (m *EventRegistryClassCreated) GetMaintainer() string { + if m != nil { + return m.Maintainer + } + return "" +} + +// EventRegistryClassUpdated is emitted when a registry class's authorization rules are updated. +type EventRegistryClassUpdated struct { + RegistryClassId string `protobuf:"bytes,1,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` + Maintainer string `protobuf:"bytes,2,opt,name=maintainer,proto3" json:"maintainer,omitempty"` +} + +func (m *EventRegistryClassUpdated) Reset() { *m = EventRegistryClassUpdated{} } +func (m *EventRegistryClassUpdated) String() string { return proto.CompactTextString(m) } +func (*EventRegistryClassUpdated) ProtoMessage() {} +func (*EventRegistryClassUpdated) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{9} +} +func (m *EventRegistryClassUpdated) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRegistryClassUpdated) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRegistryClassUpdated.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRegistryClassUpdated) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRegistryClassUpdated.Merge(m, src) +} +func (m *EventRegistryClassUpdated) XXX_Size() int { + return m.Size() +} +func (m *EventRegistryClassUpdated) XXX_DiscardUnknown() { + xxx_messageInfo_EventRegistryClassUpdated.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRegistryClassUpdated proto.InternalMessageInfo + +func (m *EventRegistryClassUpdated) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +func (m *EventRegistryClassUpdated) GetMaintainer() string { + if m != nil { + return m.Maintainer + } + return "" +} + func init() { proto.RegisterType((*EventNFTRegistered)(nil), "provenance.registry.v1.EventNFTRegistered") proto.RegisterType((*EventRoleGranted)(nil), "provenance.registry.v1.EventRoleGranted") @@ -511,6 +625,8 @@ func init() { proto.RegisterType((*EventRoleChangeProposed)(nil), "provenance.registry.v1.EventRoleChangeProposed") proto.RegisterType((*EventRoleChangeApproved)(nil), "provenance.registry.v1.EventRoleChangeApproved") proto.RegisterType((*EventRoleChangeApplied)(nil), "provenance.registry.v1.EventRoleChangeApplied") + proto.RegisterType((*EventRegistryClassCreated)(nil), "provenance.registry.v1.EventRegistryClassCreated") + proto.RegisterType((*EventRegistryClassUpdated)(nil), "provenance.registry.v1.EventRegistryClassUpdated") } func init() { @@ -518,31 +634,35 @@ func init() { } var fileDescriptor_61a0995529587ff0 = []byte{ - // 382 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x53, 0xcd, 0x4e, 0xea, 0x40, - 0x18, 0x65, 0x2e, 0x5c, 0x42, 0x27, 0x37, 0x37, 0x37, 0x93, 0x2b, 0xd6, 0x9f, 0x34, 0xa4, 0xba, - 0x60, 0x63, 0x1b, 0xa2, 0x2f, 0x20, 0x44, 0x0d, 0x1b, 0x83, 0x55, 0x62, 0xe2, 0x86, 0x0c, 0x9d, - 0x01, 0x1a, 0xea, 0x4c, 0x33, 0x33, 0x34, 0xb2, 0xf4, 0x09, 0xf4, 0xb1, 0x5c, 0xb2, 0x74, 0x69, - 0xe0, 0x45, 0x4c, 0xa7, 0x40, 0x55, 0xdc, 0xa1, 0xee, 0xfa, 0x9d, 0x73, 0x72, 0x7a, 0xbe, 0xd3, - 0x7e, 0x70, 0x2f, 0x12, 0x3c, 0xa6, 0x0c, 0x33, 0x9f, 0xba, 0x82, 0xf6, 0x03, 0xa9, 0xc4, 0xd8, - 0x8d, 0x6b, 0x2e, 0x8d, 0x29, 0x53, 0xd2, 0x89, 0x04, 0x57, 0x1c, 0x95, 0x33, 0x91, 0xb3, 0x10, - 0x39, 0x71, 0xcd, 0xbe, 0x80, 0xe8, 0x24, 0xd1, 0x9d, 0x9f, 0x5e, 0x79, 0x1a, 0xa6, 0x82, 0x12, - 0xb4, 0x01, 0x8b, 0xac, 0xa7, 0x3a, 0x01, 0x31, 0x41, 0x05, 0x54, 0x0d, 0xef, 0x37, 0xeb, 0xa9, - 0x26, 0x41, 0xfb, 0xf0, 0x2f, 0x96, 0x92, 0xaa, 0x8e, 0x1f, 0x62, 0x29, 0x13, 0xfa, 0x97, 0xa6, - 0xff, 0x68, 0xb4, 0x91, 0x80, 0x4d, 0x62, 0xdf, 0x03, 0xf8, 0x4f, 0x7b, 0x7a, 0x3c, 0xa4, 0x67, - 0x02, 0x33, 0xb5, 0xa6, 0x23, 0x42, 0xb0, 0x20, 0x78, 0x48, 0xcd, 0xbc, 0xe6, 0xf4, 0x33, 0xda, - 0x85, 0x06, 0x26, 0x44, 0x50, 0x29, 0xa9, 0x34, 0x0b, 0x95, 0x7c, 0xd5, 0xf0, 0x32, 0xe0, 0x7d, - 0x06, 0x8f, 0xc6, 0x7c, 0xf8, 0xf3, 0x19, 0x2e, 0xe1, 0xff, 0x45, 0xb5, 0x6d, 0x26, 0xbe, 0xa8, - 0xdc, 0x6b, 0x68, 0xa6, 0x7b, 0xcd, 0xbf, 0x61, 0x7d, 0x14, 0x0e, 0xdb, 0x11, 0xc1, 0xeb, 0x76, - 0x6c, 0x3f, 0x00, 0xb8, 0xb9, 0x6c, 0xac, 0x31, 0xc0, 0xac, 0x4f, 0x5b, 0x82, 0x47, 0x5c, 0xae, - 0x5b, 0xdc, 0x0e, 0x34, 0x7c, 0x6d, 0x97, 0x08, 0xd2, 0xf6, 0x4a, 0x29, 0xd0, 0x24, 0x68, 0x1b, - 0x96, 0xa2, 0xf4, 0x2d, 0xc2, 0x2c, 0xa4, 0xdc, 0x62, 0xb6, 0xbd, 0x95, 0x40, 0xc7, 0x91, 0xfe, - 0x8b, 0x3f, 0x78, 0x82, 0x55, 0x4f, 0x9c, 0x0a, 0xc5, 0x3c, 0xd0, 0x72, 0xb6, 0x05, 0x2c, 0xaf, - 0x7a, 0x86, 0xc1, 0x77, 0xee, 0x58, 0x1f, 0x3e, 0x4d, 0x2d, 0x30, 0x99, 0x5a, 0xe0, 0x65, 0x6a, - 0x81, 0xc7, 0x99, 0x95, 0x9b, 0xcc, 0xac, 0xdc, 0xf3, 0xcc, 0xca, 0xc1, 0xad, 0x80, 0x3b, 0x9f, - 0xdf, 0x65, 0x0b, 0xdc, 0x1c, 0xf5, 0x03, 0x35, 0x18, 0x75, 0x1d, 0x9f, 0xdf, 0xba, 0x99, 0xe8, - 0x20, 0xe0, 0x6f, 0x26, 0xf7, 0x2e, 0xbb, 0x78, 0x35, 0x8e, 0xa8, 0xec, 0x16, 0xf5, 0xb9, 0x1f, - 0xbe, 0x06, 0x00, 0x00, 0xff, 0xff, 0x0f, 0x6a, 0x22, 0x48, 0x15, 0x04, 0x00, 0x00, + // 444 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x4d, 0x6f, 0xd4, 0x30, + 0x10, 0x5d, 0xb7, 0x4b, 0xd5, 0x1d, 0x21, 0x3e, 0x2c, 0x28, 0x29, 0xa0, 0xa8, 0x0a, 0x1c, 0x2a, + 0x24, 0x12, 0x55, 0xf0, 0x07, 0xe8, 0x0a, 0xd0, 0x5e, 0x50, 0x09, 0x54, 0x48, 0x5c, 0x2a, 0x77, + 0x3d, 0x4d, 0xad, 0x4d, 0xed, 0x68, 0xec, 0x46, 0xf4, 0xc8, 0x0f, 0x40, 0xf0, 0xb3, 0x38, 0xf6, + 0xc8, 0x11, 0xed, 0xfe, 0x11, 0x14, 0x67, 0xd3, 0xec, 0x76, 0x7b, 0x00, 0x2d, 0x70, 0x8b, 0xdf, + 0x3c, 0xbf, 0x79, 0x7e, 0x76, 0x06, 0x1e, 0x15, 0x64, 0x4a, 0xd4, 0x42, 0x0f, 0x31, 0x21, 0xcc, + 0x94, 0x75, 0x74, 0x96, 0x94, 0x3b, 0x09, 0x96, 0xa8, 0x9d, 0x8d, 0x0b, 0x32, 0xce, 0xf0, 0x8d, + 0x96, 0x14, 0x37, 0xa4, 0xb8, 0xdc, 0x89, 0xde, 0x02, 0x7f, 0x59, 0xf1, 0xde, 0xbc, 0x7a, 0x9f, + 0x7a, 0x18, 0x09, 0x25, 0xbf, 0x0b, 0x6b, 0xfa, 0xc8, 0x1d, 0x28, 0x19, 0xb0, 0x2d, 0xb6, 0xdd, + 0x4b, 0xaf, 0xe9, 0x23, 0x37, 0x90, 0xfc, 0x31, 0xdc, 0x10, 0xd6, 0xa2, 0x3b, 0x18, 0xe6, 0xc2, + 0xda, 0xaa, 0xbc, 0xe2, 0xcb, 0xd7, 0x3d, 0xda, 0xaf, 0xc0, 0x81, 0x8c, 0x3e, 0x33, 0xb8, 0xe5, + 0x35, 0x53, 0x93, 0xe3, 0x6b, 0x12, 0xda, 0x2d, 0xa9, 0xc8, 0x39, 0x74, 0xc9, 0xe4, 0x18, 0xac, + 0xfa, 0x9a, 0xff, 0xe6, 0x0f, 0xa1, 0x27, 0xa4, 0x24, 0xb4, 0x16, 0x6d, 0xd0, 0xdd, 0x5a, 0xdd, + 0xee, 0xa5, 0x2d, 0x30, 0xef, 0x21, 0xc5, 0xd2, 0x8c, 0xfe, 0xbf, 0x87, 0x77, 0x70, 0xa7, 0x89, + 0x76, 0x5f, 0xd3, 0x5f, 0x0a, 0xf7, 0x03, 0x04, 0xf5, 0xb9, 0xa6, 0x77, 0xb8, 0x7b, 0x9a, 0x8f, + 0xf6, 0x0b, 0x29, 0x96, 0xcd, 0x38, 0xfa, 0xca, 0xe0, 0xde, 0x45, 0x62, 0xfd, 0x63, 0xa1, 0x33, + 0xdc, 0x23, 0x53, 0x18, 0xbb, 0x6c, 0x70, 0x0f, 0xa0, 0x37, 0xf4, 0x72, 0x15, 0xa1, 0x4e, 0x6f, + 0xbd, 0x06, 0x06, 0x92, 0xdf, 0x87, 0xf5, 0xa2, 0xee, 0x42, 0x41, 0xb7, 0xae, 0x35, 0xeb, 0x28, + 0x5d, 0x30, 0xf4, 0xa2, 0xf0, 0xaf, 0xf8, 0x92, 0x26, 0x5b, 0xd4, 0x14, 0x35, 0x91, 0xa6, 0x86, + 0x2e, 0xd6, 0x11, 0xc1, 0xc6, 0xa2, 0x66, 0xae, 0xfe, 0xe5, 0x19, 0xa3, 0x2f, 0x0c, 0x36, 0xe7, + 0xee, 0xcc, 0xef, 0xea, 0x13, 0xfa, 0x4b, 0x7b, 0x02, 0xb7, 0x9b, 0xff, 0xb1, 0xed, 0x51, 0x5b, + 0xb8, 0x49, 0xb3, 0x1b, 0x7e, 0xdb, 0x4c, 0x08, 0x70, 0x22, 0x94, 0x76, 0x42, 0x69, 0xa4, 0xa9, + 0x9b, 0x19, 0x24, 0xca, 0xae, 0xb2, 0xd3, 0xbc, 0xa1, 0x3f, 0xb1, 0x33, 0xdf, 0x68, 0xe5, 0x72, + 0xa3, 0xdd, 0xd1, 0xf7, 0x71, 0xc8, 0xce, 0xc7, 0x21, 0xfb, 0x39, 0x0e, 0xd9, 0xb7, 0x49, 0xd8, + 0x39, 0x9f, 0x84, 0x9d, 0x1f, 0x93, 0xb0, 0x03, 0x9b, 0xca, 0xc4, 0x57, 0x0f, 0xa4, 0x3d, 0xf6, + 0xf1, 0x79, 0xa6, 0xdc, 0xf1, 0xe9, 0x61, 0x3c, 0x34, 0x27, 0x49, 0x4b, 0x7a, 0xaa, 0xcc, 0xcc, + 0x2a, 0xf9, 0xd4, 0x8e, 0x3a, 0x77, 0x56, 0xa0, 0x3d, 0x5c, 0xf3, 0x73, 0xee, 0xd9, 0xaf, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xe7, 0x09, 0x9d, 0xa4, 0x0e, 0x05, 0x00, 0x00, } func (m *EventNFTRegistered) Marshal() (dAtA []byte, err error) { @@ -894,6 +1014,87 @@ func (m *EventRoleChangeApplied) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *EventRegistryClassCreated) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRegistryClassCreated) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRegistryClassCreated) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Maintainer) > 0 { + i -= len(m.Maintainer) + copy(dAtA[i:], m.Maintainer) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Maintainer))) + i-- + dAtA[i] = 0x1a + } + if len(m.AssetClassId) > 0 { + i -= len(m.AssetClassId) + copy(dAtA[i:], m.AssetClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.AssetClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventRegistryClassUpdated) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRegistryClassUpdated) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRegistryClassUpdated) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Maintainer) > 0 { + i -= len(m.Maintainer) + copy(dAtA[i:], m.Maintainer) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Maintainer))) + i-- + dAtA[i] = 0x12 + } + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -1073,6 +1274,44 @@ func (m *EventRoleChangeApplied) Size() (n int) { return n } +func (m *EventRegistryClassCreated) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Maintainer) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + +func (m *EventRegistryClassUpdated) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Maintainer) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2215,6 +2454,266 @@ func (m *EventRoleChangeApplied) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventRegistryClassCreated) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRegistryClassCreated: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRegistryClassCreated: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Maintainer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Maintainer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventRegistryClassUpdated) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRegistryClassUpdated: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRegistryClassUpdated: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Maintainer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Maintainer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/genesis.go b/x/registry/types/genesis.go index 14510947c5..2906668138 100644 --- a/x/registry/types/genesis.go +++ b/x/registry/types/genesis.go @@ -15,5 +15,16 @@ func (m *GenesisState) Validate() error { } } + seenClasses := make(map[string]bool, len(m.RegistryClasses)) + for _, class := range m.RegistryClasses { + if err := class.Validate(); err != nil { + return fmt.Errorf("registry class: %w", err) + } + if seenClasses[class.RegistryClassId] { + return fmt.Errorf("duplicate registry class id: %q", class.RegistryClassId) + } + seenClasses[class.RegistryClassId] = true + } + return nil } diff --git a/x/registry/types/genesis.pb.go b/x/registry/types/genesis.pb.go index 0f81542152..81891f5008 100644 --- a/x/registry/types/genesis.pb.go +++ b/x/registry/types/genesis.pb.go @@ -32,6 +32,8 @@ type GenesisState struct { // pending_role_changes is the list of role changes awaiting approval. // These preserve in-flight multi-party role changes across genesis export/import. PendingRoleChanges []PendingRoleChange `protobuf:"bytes,2,rep,name=pending_role_changes,json=pendingRoleChanges,proto3" json:"pending_role_changes"` + // registry_classes is the list of registry classes defining asset class-level authorization rules. + RegistryClasses []RegistryClass `protobuf:"bytes,3,rep,name=registry_classes,json=registryClasses,proto3" json:"registry_classes"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -81,6 +83,13 @@ func (m *GenesisState) GetPendingRoleChanges() []PendingRoleChange { return nil } +func (m *GenesisState) GetRegistryClasses() []RegistryClass { + if m != nil { + return m.RegistryClasses + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "provenance.registry.v1.GenesisState") } @@ -90,24 +99,27 @@ func init() { } var fileDescriptor_1652bcf54f1aa9cb = []byte{ - // 266 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x29, 0x28, 0xca, 0x2f, - 0x4b, 0xcd, 0x4b, 0xcc, 0x4b, 0x4e, 0xd5, 0x2f, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, 0x29, 0xaa, 0xd4, - 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0x12, 0x43, 0xa8, 0xd2, 0x83, 0xa9, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, - 0x07, 0x2b, 0xd1, 0x07, 0xb1, 0x20, 0xaa, 0xa5, 0x54, 0x71, 0x98, 0x09, 0xd7, 0x09, 0x56, 0xa6, - 0xb4, 0x83, 0x91, 0x8b, 0xc7, 0x1d, 0x62, 0x4d, 0x70, 0x49, 0x62, 0x49, 0xaa, 0x90, 0x2b, 0x17, - 0x7b, 0x6a, 0x5e, 0x49, 0x51, 0x66, 0x6a, 0xb1, 0x04, 0xa3, 0x02, 0xb3, 0x06, 0xb7, 0x91, 0xaa, - 0x1e, 0x76, 0x7b, 0xf5, 0x82, 0xa0, 0x6c, 0xd7, 0xbc, 0x92, 0xa2, 0x4a, 0x27, 0x96, 0x13, 0xf7, - 0xe4, 0x19, 0x82, 0x60, 0x7a, 0x85, 0x12, 0xb9, 0x44, 0x0a, 0x52, 0xf3, 0x52, 0x32, 0xf3, 0xd2, - 0xe3, 0x8b, 0xf2, 0x73, 0x52, 0xe3, 0x93, 0x33, 0x12, 0xf3, 0xd2, 0x53, 0x8b, 0x25, 0x98, 0xc0, - 0x66, 0x6a, 0xe2, 0x32, 0x33, 0x00, 0xa2, 0x27, 0x28, 0x3f, 0x27, 0xd5, 0x19, 0xac, 0x03, 0x6a, - 0xae, 0x50, 0x01, 0xba, 0x44, 0xb1, 0x53, 0xf6, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, - 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 0x37, 0x1e, 0xcb, - 0x31, 0x70, 0x49, 0x66, 0xe6, 0xe3, 0xb0, 0x20, 0x80, 0x31, 0xca, 0x24, 0x3d, 0xb3, 0x24, 0xa3, - 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xa1, 0x48, 0x37, 0x33, 0x1f, 0x89, 0xa7, 0x5f, 0x81, - 0x08, 0xb3, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, 0x36, 0x70, 0x70, 0x19, 0x03, 0x02, 0x00, 0x00, - 0xff, 0xff, 0x72, 0x54, 0x76, 0x01, 0xab, 0x01, 0x00, 0x00, + // 309 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0xcd, 0x4a, 0x33, 0x31, + 0x14, 0x86, 0x67, 0xda, 0x8f, 0x4f, 0x88, 0x82, 0x32, 0x14, 0xa9, 0x5d, 0x44, 0x11, 0x0b, 0x2a, + 0x98, 0x50, 0xf5, 0x0a, 0x5a, 0x8a, 0xdb, 0x52, 0xc1, 0x85, 0x9b, 0x92, 0x8e, 0x87, 0x34, 0x58, + 0x73, 0x86, 0x24, 0x2d, 0xd6, 0xab, 0xe8, 0x65, 0x75, 0xd9, 0xa5, 0x2b, 0x91, 0x99, 0x1b, 0x91, + 0xce, 0x8f, 0x53, 0xc4, 0x01, 0x77, 0x27, 0x39, 0xcf, 0xfb, 0xf0, 0xc2, 0x21, 0x67, 0x91, 0xc1, + 0x39, 0x68, 0xa1, 0x43, 0xe0, 0x06, 0xa4, 0xb2, 0xce, 0x2c, 0xf8, 0xbc, 0xc3, 0x25, 0x68, 0xb0, + 0xca, 0xb2, 0xc8, 0xa0, 0xc3, 0xe0, 0xb0, 0xa4, 0x58, 0x41, 0xb1, 0x79, 0xa7, 0xd5, 0x90, 0x28, + 0x31, 0x45, 0xf8, 0x66, 0xca, 0xe8, 0x56, 0xbb, 0xc2, 0xf9, 0x9d, 0xcc, 0xb0, 0xcb, 0x0a, 0x4c, + 0xcc, 0xdc, 0x04, 0x8d, 0x7a, 0x13, 0x4e, 0xa1, 0xce, 0xd8, 0xd3, 0x65, 0x8d, 0xec, 0xdd, 0x65, + 0x95, 0xee, 0x9d, 0x70, 0x10, 0xf4, 0xc9, 0x0e, 0x68, 0x67, 0x14, 0xd8, 0xa6, 0x7f, 0x52, 0x3f, + 0xdf, 0xbd, 0x6e, 0xb3, 0xdf, 0x3b, 0xb2, 0x61, 0x3e, 0xf7, 0xb5, 0x33, 0x8b, 0xee, 0xbf, 0xd5, + 0xc7, 0xb1, 0x37, 0x2c, 0xb2, 0x81, 0x20, 0x8d, 0x08, 0xf4, 0x93, 0xd2, 0x72, 0x64, 0x70, 0x0a, + 0xa3, 0x70, 0x22, 0xb4, 0x04, 0xdb, 0xac, 0xa5, 0xce, 0x8b, 0x2a, 0xe7, 0x20, 0xcb, 0x0c, 0x71, + 0x0a, 0xbd, 0x34, 0x91, 0x7b, 0x83, 0xe8, 0xe7, 0xc2, 0x06, 0x0f, 0xe4, 0xa0, 0x88, 0x8e, 0xc2, + 0xa9, 0xb0, 0x16, 0x6c, 0xb3, 0xfe, 0xb7, 0xca, 0xbd, 0x0d, 0x9e, 0xab, 0xf7, 0xcd, 0xf6, 0x27, + 0xd8, 0xee, 0xf3, 0x2a, 0xa6, 0xfe, 0x3a, 0xa6, 0xfe, 0x67, 0x4c, 0xfd, 0x65, 0x42, 0xbd, 0x75, + 0x42, 0xbd, 0xf7, 0x84, 0x7a, 0xe4, 0x48, 0x61, 0x85, 0x79, 0xe0, 0x3f, 0xde, 0x4a, 0xe5, 0x26, + 0xb3, 0x31, 0x0b, 0xf1, 0x85, 0x97, 0xd0, 0x95, 0xc2, 0xad, 0x17, 0x7f, 0x2d, 0x0f, 0xe2, 0x16, + 0x11, 0xd8, 0xf1, 0xff, 0xf4, 0x0c, 0x37, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x78, 0xdf, + 0x24, 0x2f, 0x02, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -130,6 +142,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.RegistryClasses) > 0 { + for iNdEx := len(m.RegistryClasses) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RegistryClasses[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } if len(m.PendingRoleChanges) > 0 { for iNdEx := len(m.PendingRoleChanges) - 1; iNdEx >= 0; iNdEx-- { { @@ -190,6 +216,12 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + if len(m.RegistryClasses) > 0 { + for _, e := range m.RegistryClasses { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -296,6 +328,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClasses", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClasses = append(m.RegistryClasses, RegistryClass{}) + if err := m.RegistryClasses[len(m.RegistryClasses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 7a842bffd2..88be15db86 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -17,6 +17,8 @@ var AllRequestMsgs = []sdk.Msg{ (*MsgSetRoles)(nil), (*MsgProposeRoleChange)(nil), (*MsgApproveRoleChange)(nil), + (*MsgCreateRegistryClass)(nil), + (*MsgUpdateRegistryClassRoleAuthorization)(nil), } // ValidateBasic validates the MsgRegisterNFT message @@ -38,6 +40,13 @@ func (m MsgRegisterNFT) ValidateBasic() error { } } + // registry_class_id is optional; validate format only when set. + if m.RegistryClassId != "" { + if err := ValidateRegistryClassID(m.RegistryClassId); err != nil { + errs = append(errs, NewErrCodeInvalidField("registry_class_id", "%s", err)) + } + } + return errors.Join(errs...) } @@ -167,6 +176,51 @@ func (m MsgApproveRoleChange) ValidateBasic() error { return errors.Join(errs...) } +// ValidateBasic validates the MsgCreateRegistryClass message +func (m MsgCreateRegistryClass) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + } + + if _, err := sdk.AccAddressFromBech32(m.Maintainer); err != nil { + errs = append(errs, NewErrCodeInvalidField("maintainer", "%s", err)) + } + + // The signer must be the maintainer being set. + if m.Signer != m.Maintainer { + errs = append(errs, NewErrCodeInvalidField("signer", "signer %q must match maintainer %q", m.Signer, m.Maintainer)) + } + + if err := ValidateRegistryClassID(m.RegistryClassId); err != nil { + errs = append(errs, NewErrCodeInvalidField("registry_class_id", "%s", err)) + } + + if err := ValidateClassID(m.AssetClassId); err != nil { + errs = append(errs, NewErrCodeInvalidField("asset_class_id", "%s", err)) + } + + errs = append(errs, validateRoleAuthorizations(m.RoleAuthorizations)...) + + return errors.Join(errs...) +} + +// ValidateBasic validates the MsgUpdateRegistryClassRoleAuthorization message +func (m MsgUpdateRegistryClassRoleAuthorization) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + } + + if err := ValidateRegistryClassID(m.RegistryClassId); err != nil { + errs = append(errs, NewErrCodeInvalidField("registry_class_id", "%s", err)) + } + + errs = append(errs, validateRoleAuthorizations(m.RoleAuthorizations)...) + + return errors.Join(errs...) +} + // ValidateBasic validates the MsgUnregisterNFT message func (m MsgUnregisterNFT) ValidateBasic() error { var errs []error @@ -268,6 +322,20 @@ func (m QueryPendingRoleChangesRequest) Validate() error { return nil } +// Validate validates the QueryRegistryClassRequest +func (m QueryRegistryClassRequest) Validate() error { + if len(m.RegistryClassId) == 0 { + return NewErrCodeInvalidField("registry_class_id", "registry_class_id cannot be empty") + } + return nil +} + +// Validate validates the QueryRegistryClassesRequest +func (m QueryRegistryClassesRequest) Validate() error { + // There's nothing to validate; pagination is optional. + return nil +} + // ValidateStringLength checks several conditions in order: // 1. length is not between the minimum and maximum length returns an ErrCodeInvalidField error. // 2. contains whitespace returns an ErrCodeInvalidField error. diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 6fd6bd4894..64759a1e01 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -20,6 +20,8 @@ func TestAllMsgsGetSigners(t *testing.T) { func(signer string) sdk.Msg { return &MsgGrantRole{Signer: signer} }, func(signer string) sdk.Msg { return &MsgRevokeRole{Signer: signer} }, func(signer string) sdk.Msg { return &MsgSetRoles{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgCreateRegistryClass{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgUpdateRegistryClassRoleAuthorization{Signer: signer} }, } msgMakersMulti := []testutil.MsgMakerMulti{} diff --git a/x/registry/types/query.pb.go b/x/registry/types/query.pb.go index b468252d01..eea08e3232 100644 --- a/x/registry/types/query.pb.go +++ b/x/registry/types/query.pb.go @@ -560,6 +560,199 @@ func (m *QueryPendingRoleChangesResponse) GetPagination() *query.PageResponse { return nil } +// QueryRegistryClassRequest is the request type for the Query/RegistryClass RPC method. +type QueryRegistryClassRequest struct { + // registry_class_id is the unique identifier of the registry class to retrieve. + RegistryClassId string `protobuf:"bytes,1,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` +} + +func (m *QueryRegistryClassRequest) Reset() { *m = QueryRegistryClassRequest{} } +func (m *QueryRegistryClassRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRegistryClassRequest) ProtoMessage() {} +func (*QueryRegistryClassRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{10} +} +func (m *QueryRegistryClassRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRegistryClassRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRegistryClassRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRegistryClassRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRegistryClassRequest.Merge(m, src) +} +func (m *QueryRegistryClassRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryRegistryClassRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRegistryClassRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRegistryClassRequest proto.InternalMessageInfo + +func (m *QueryRegistryClassRequest) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +// QueryRegistryClassResponse is the response type for the Query/RegistryClass RPC method. +type QueryRegistryClassResponse struct { + // registry_class is the registry class for the requested id, including its authorization policy. + RegistryClass RegistryClass `protobuf:"bytes,1,opt,name=registry_class,json=registryClass,proto3" json:"registry_class"` +} + +func (m *QueryRegistryClassResponse) Reset() { *m = QueryRegistryClassResponse{} } +func (m *QueryRegistryClassResponse) String() string { return proto.CompactTextString(m) } +func (*QueryRegistryClassResponse) ProtoMessage() {} +func (*QueryRegistryClassResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{11} +} +func (m *QueryRegistryClassResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRegistryClassResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRegistryClassResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRegistryClassResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRegistryClassResponse.Merge(m, src) +} +func (m *QueryRegistryClassResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryRegistryClassResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRegistryClassResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRegistryClassResponse proto.InternalMessageInfo + +func (m *QueryRegistryClassResponse) GetRegistryClass() RegistryClass { + if m != nil { + return m.RegistryClass + } + return RegistryClass{} +} + +// QueryRegistryClassesRequest is the paginated request type for the Query/RegistryClasses RPC method. +type QueryRegistryClassesRequest struct { + // pagination defines an optional pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,99,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryRegistryClassesRequest) Reset() { *m = QueryRegistryClassesRequest{} } +func (m *QueryRegistryClassesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryRegistryClassesRequest) ProtoMessage() {} +func (*QueryRegistryClassesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{12} +} +func (m *QueryRegistryClassesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRegistryClassesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRegistryClassesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRegistryClassesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRegistryClassesRequest.Merge(m, src) +} +func (m *QueryRegistryClassesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryRegistryClassesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRegistryClassesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRegistryClassesRequest proto.InternalMessageInfo + +func (m *QueryRegistryClassesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryRegistryClassesResponse is the paginated response type for the Query/RegistryClasses RPC method. +type QueryRegistryClassesResponse struct { + // registry_classes is the collection of registry classes. + RegistryClasses []RegistryClass `protobuf:"bytes,1,rep,name=registry_classes,json=registryClasses,proto3" json:"registry_classes"` + // pagination is the pagination details for this response. + Pagination *query.PageResponse `protobuf:"bytes,99,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryRegistryClassesResponse) Reset() { *m = QueryRegistryClassesResponse{} } +func (m *QueryRegistryClassesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryRegistryClassesResponse) ProtoMessage() {} +func (*QueryRegistryClassesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{13} +} +func (m *QueryRegistryClassesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryRegistryClassesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryRegistryClassesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryRegistryClassesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryRegistryClassesResponse.Merge(m, src) +} +func (m *QueryRegistryClassesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryRegistryClassesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryRegistryClassesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryRegistryClassesResponse proto.InternalMessageInfo + +func (m *QueryRegistryClassesResponse) GetRegistryClasses() []RegistryClass { + if m != nil { + return m.RegistryClasses + } + return nil +} + +func (m *QueryRegistryClassesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + func init() { proto.RegisterType((*QueryGetRegistryRequest)(nil), "provenance.registry.v1.QueryGetRegistryRequest") proto.RegisterType((*QueryGetRegistryResponse)(nil), "provenance.registry.v1.QueryGetRegistryResponse") @@ -571,6 +764,10 @@ func init() { proto.RegisterType((*QueryPendingRoleChangeResponse)(nil), "provenance.registry.v1.QueryPendingRoleChangeResponse") proto.RegisterType((*QueryPendingRoleChangesRequest)(nil), "provenance.registry.v1.QueryPendingRoleChangesRequest") proto.RegisterType((*QueryPendingRoleChangesResponse)(nil), "provenance.registry.v1.QueryPendingRoleChangesResponse") + proto.RegisterType((*QueryRegistryClassRequest)(nil), "provenance.registry.v1.QueryRegistryClassRequest") + proto.RegisterType((*QueryRegistryClassResponse)(nil), "provenance.registry.v1.QueryRegistryClassResponse") + proto.RegisterType((*QueryRegistryClassesRequest)(nil), "provenance.registry.v1.QueryRegistryClassesRequest") + proto.RegisterType((*QueryRegistryClassesResponse)(nil), "provenance.registry.v1.QueryRegistryClassesResponse") } func init() { @@ -578,55 +775,65 @@ func init() { } var fileDescriptor_c166c561e401a2eb = []byte{ - // 760 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x96, 0xcf, 0x4f, 0x13, 0x41, - 0x14, 0xc7, 0x3b, 0x05, 0x2d, 0x3e, 0x94, 0x84, 0x81, 0x68, 0x69, 0xb4, 0x34, 0x2b, 0x28, 0x2a, - 0xee, 0xd0, 0x0a, 0xca, 0x19, 0x54, 0x54, 0x2e, 0x75, 0x8d, 0x17, 0x3d, 0x34, 0xd3, 0x76, 0xdc, - 0x6e, 0x28, 0x3b, 0xcb, 0xce, 0xd2, 0xd8, 0x34, 0x1c, 0xf4, 0xe6, 0xcd, 0xc4, 0x3f, 0x40, 0xce, - 0x1e, 0xbd, 0xea, 0xc5, 0x1b, 0x17, 0x13, 0x12, 0x2f, 0x9e, 0x8c, 0x01, 0xff, 0x10, 0xb3, 0xb3, - 0xd3, 0x5f, 0xb4, 0x4b, 0xd9, 0xc0, 0xad, 0x3b, 0x33, 0xdf, 0x79, 0x9f, 0xef, 0x7b, 0x6f, 0x5e, - 0x0a, 0x9a, 0xe3, 0xf2, 0x1a, 0xb3, 0xa9, 0x5d, 0x62, 0xc4, 0x65, 0xa6, 0x25, 0x3c, 0xb7, 0x4e, - 0x6a, 0x59, 0xb2, 0xb5, 0xcd, 0xdc, 0xba, 0xee, 0xb8, 0xdc, 0xe3, 0xf8, 0x72, 0xfb, 0x8c, 0xde, - 0x3c, 0xa3, 0xd7, 0xb2, 0xa9, 0xdb, 0x25, 0x2e, 0x36, 0xb9, 0x20, 0x45, 0x2a, 0x58, 0x20, 0x20, - 0xb5, 0x6c, 0x91, 0x79, 0x34, 0x4b, 0x1c, 0x6a, 0x5a, 0x36, 0xf5, 0x2c, 0x6e, 0x07, 0x77, 0xa4, - 0x26, 0x4d, 0x6e, 0x72, 0xf9, 0x93, 0xf8, 0xbf, 0xd4, 0xea, 0x55, 0x93, 0x73, 0xb3, 0xca, 0x08, - 0x75, 0x2c, 0x42, 0x6d, 0x9b, 0x7b, 0x52, 0x22, 0xd4, 0xee, 0x6c, 0x08, 0x5b, 0x8b, 0x41, 0x1e, - 0xd3, 0xf2, 0x70, 0xe5, 0xb9, 0x1f, 0x7c, 0x8d, 0x79, 0x86, 0xda, 0x31, 0xd8, 0xd6, 0x36, 0x13, - 0x1e, 0x5e, 0x82, 0xa1, 0x0d, 0x56, 0x4f, 0xa2, 0x0c, 0x9a, 0x1b, 0xcd, 0x5d, 0xd7, 0xfb, 0xfb, - 0xd0, 0x9b, 0xaa, 0x75, 0x56, 0x37, 0xfc, 0xf3, 0x5a, 0x09, 0x92, 0xbd, 0x37, 0x0a, 0x87, 0xdb, - 0x82, 0xe1, 0x35, 0x18, 0x69, 0x6a, 0xd5, 0xbd, 0xb3, 0x83, 0xee, 0x7d, 0x64, 0x7b, 0x6e, 0x7d, - 0x65, 0x78, 0xef, 0xcf, 0x74, 0xcc, 0x68, 0x89, 0xb5, 0x0f, 0x08, 0xa6, 0x8e, 0x44, 0xb1, 0x98, - 0x68, 0x92, 0xcf, 0xc0, 0x18, 0x15, 0x82, 0x79, 0x85, 0x52, 0x95, 0x0a, 0x51, 0xb0, 0xca, 0x32, - 0xd8, 0x05, 0xe3, 0xa2, 0x5c, 0x5d, 0xf5, 0x17, 0x9f, 0x96, 0xf1, 0x63, 0x80, 0x76, 0xa6, 0x93, - 0x25, 0x89, 0x73, 0x43, 0x0f, 0xca, 0xa2, 0xfb, 0x65, 0xd1, 0x83, 0x3a, 0xaa, 0xb2, 0xe8, 0x79, - 0x6a, 0x32, 0x15, 0xc1, 0xe8, 0x50, 0x6a, 0x5f, 0x11, 0xa4, 0xfa, 0xb1, 0x28, 0xcf, 0xeb, 0x00, - 0x6e, 0x6b, 0x35, 0x89, 0x32, 0x43, 0x51, 0x5d, 0x77, 0xc8, 0xf1, 0x5a, 0x1f, 0xe6, 0x9b, 0x03, - 0x99, 0x03, 0x92, 0x2e, 0xe8, 0x5d, 0x04, 0x13, 0x12, 0xfa, 0x09, 0x15, 0x06, 0xaf, 0xb2, 0xd3, - 0x15, 0x1d, 0x27, 0x21, 0x41, 0xcb, 0x65, 0x97, 0x09, 0x91, 0x8c, 0xcb, 0x54, 0x37, 0x3f, 0xf1, - 0x32, 0x0c, 0xbb, 0xbc, 0xca, 0x92, 0x43, 0x19, 0x34, 0x37, 0x96, 0x9b, 0x19, 0x74, 0xa3, 0x64, - 0x91, 0x0a, 0x2d, 0x0b, 0x93, 0xdd, 0x84, 0x2a, 0xa1, 0x53, 0x30, 0x52, 0xa1, 0xa2, 0x20, 0x6f, - 0xf5, 0x39, 0x47, 0x8c, 0x44, 0x25, 0x38, 0xa2, 0x11, 0xb8, 0x26, 0x25, 0x79, 0x66, 0x97, 0x2d, - 0xdb, 0xf4, 0xd7, 0x56, 0x2b, 0xd4, 0x6e, 0xd5, 0x0d, 0x8f, 0x41, 0xbc, 0xd5, 0x0d, 0x71, 0xab, - 0xac, 0xbd, 0x43, 0x90, 0x0e, 0x53, 0xa8, 0x70, 0x05, 0x98, 0x70, 0x82, 0x4d, 0x19, 0xb2, 0x50, - 0x92, 0xdb, 0x2a, 0x43, 0xb7, 0xc2, 0xfc, 0xf4, 0xdc, 0xa7, 0x8a, 0x39, 0xee, 0x1c, 0xdd, 0xd0, - 0x3e, 0x87, 0x32, 0x88, 0x53, 0x56, 0xe5, 0xac, 0x3a, 0xfc, 0x27, 0x82, 0xe9, 0x50, 0x42, 0x95, - 0x26, 0x0a, 0x93, 0x7d, 0xd2, 0xd4, 0x6c, 0xf8, 0xc8, 0x79, 0xc2, 0x3d, 0x79, 0x3a, 0xbb, 0xe6, - 0xcf, 0x7d, 0x49, 0xc0, 0x39, 0xe9, 0x07, 0x7f, 0x47, 0x30, 0xda, 0x31, 0xa8, 0x30, 0x09, 0xe3, - 0x0c, 0x19, 0x92, 0xa9, 0x85, 0x93, 0x0b, 0x02, 0x10, 0xed, 0xd9, 0xfb, 0x5f, 0xff, 0x3e, 0xc5, - 0x1f, 0xe2, 0x15, 0x32, 0x60, 0x42, 0x93, 0xc6, 0x06, 0xab, 0xeb, 0xdd, 0x83, 0x6c, 0x27, 0x58, - 0xb4, 0xdf, 0x78, 0xfe, 0x07, 0xde, 0x45, 0x70, 0xa9, 0x6b, 0xea, 0xe0, 0xec, 0x09, 0x79, 0xda, - 0xd3, 0x32, 0x95, 0x8b, 0x22, 0x51, 0x26, 0xe6, 0xa4, 0x09, 0x0d, 0x67, 0x06, 0x99, 0xc0, 0x3f, - 0x10, 0x24, 0xd4, 0x0b, 0xc6, 0x77, 0x8e, 0x8d, 0xd4, 0x3d, 0x89, 0x52, 0xf3, 0x27, 0x3b, 0xac, - 0x80, 0x5e, 0x4b, 0xa0, 0x97, 0xf8, 0x45, 0x18, 0x50, 0x73, 0x64, 0x0c, 0xce, 0x2a, 0x69, 0xa8, - 0xd9, 0xb5, 0x43, 0x1a, 0xbe, 0x62, 0xc7, 0xef, 0x92, 0xf1, 0x9e, 0x46, 0xc5, 0x4b, 0xc7, 0x02, - 0x86, 0x8d, 0xa0, 0xd4, 0xfd, 0xa8, 0x32, 0xe5, 0x70, 0x59, 0x3a, 0xcc, 0xe1, 0x85, 0x30, 0x87, - 0x7d, 0x9e, 0x1f, 0x69, 0xf8, 0x5d, 0xf2, 0x0d, 0x01, 0xee, 0x7d, 0xb9, 0x38, 0x22, 0x48, 0xab, - 0x5f, 0x1e, 0x44, 0xd6, 0x29, 0x07, 0x8b, 0xd2, 0x81, 0x8e, 0xe7, 0x23, 0x38, 0x10, 0x2b, 0x1b, - 0x7b, 0x07, 0x69, 0xb4, 0x7f, 0x90, 0x46, 0x7f, 0x0f, 0xd2, 0xe8, 0xe3, 0x61, 0x3a, 0xb6, 0x7f, - 0x98, 0x8e, 0xfd, 0x3e, 0x4c, 0xc7, 0x60, 0xca, 0xe2, 0x21, 0x28, 0x79, 0xf4, 0x6a, 0xd1, 0xb4, - 0xbc, 0xca, 0x76, 0x51, 0x2f, 0xf1, 0xcd, 0x8e, 0x70, 0x77, 0x2d, 0xde, 0x19, 0xfc, 0x6d, 0x3b, - 0xbc, 0x57, 0x77, 0x98, 0x28, 0x9e, 0x97, 0xff, 0x8a, 0xee, 0xfd, 0x0f, 0x00, 0x00, 0xff, 0xff, - 0xdb, 0x88, 0x32, 0x7c, 0xda, 0x09, 0x00, 0x00, + // 922 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x41, 0x8f, 0xdb, 0x44, + 0x14, 0xde, 0xd9, 0x2d, 0xec, 0xf2, 0xca, 0x6e, 0xd9, 0xe9, 0x0a, 0xb2, 0xa1, 0xa4, 0x91, 0x69, + 0x21, 0x84, 0xe2, 0xd9, 0xa4, 0x5b, 0xa8, 0x10, 0xa7, 0x14, 0x08, 0xa5, 0x97, 0x60, 0x04, 0x07, + 0x38, 0x44, 0x93, 0x64, 0x70, 0xac, 0x4d, 0x3d, 0xae, 0xc7, 0x89, 0x08, 0x51, 0x0e, 0x70, 0xe3, + 0x86, 0xc4, 0x85, 0x1b, 0xfd, 0x05, 0x1c, 0x38, 0x02, 0x12, 0xe2, 0xd6, 0x0b, 0x52, 0x25, 0x2e, + 0x9c, 0x10, 0xda, 0xe5, 0x87, 0x20, 0x8f, 0xc7, 0x89, 0x9d, 0xd8, 0x75, 0xac, 0xcd, 0x2d, 0x9e, + 0x99, 0xef, 0xbd, 0xef, 0xfb, 0xde, 0xdb, 0xf7, 0xb4, 0xa0, 0x39, 0x2e, 0x1f, 0x31, 0x9b, 0xda, + 0x5d, 0x46, 0x5c, 0x66, 0x5a, 0xc2, 0x73, 0xc7, 0x64, 0x54, 0x23, 0x0f, 0x86, 0xcc, 0x1d, 0xeb, + 0x8e, 0xcb, 0x3d, 0x8e, 0x9f, 0x9f, 0xbf, 0xd1, 0xc3, 0x37, 0xfa, 0xa8, 0x56, 0xac, 0x76, 0xb9, + 0xb8, 0xcf, 0x05, 0xe9, 0x50, 0xc1, 0x02, 0x00, 0x19, 0xd5, 0x3a, 0xcc, 0xa3, 0x35, 0xe2, 0x50, + 0xd3, 0xb2, 0xa9, 0x67, 0x71, 0x3b, 0x88, 0x51, 0x3c, 0x30, 0xb9, 0xc9, 0xe5, 0x4f, 0xe2, 0xff, + 0x52, 0xa7, 0x57, 0x4c, 0xce, 0xcd, 0x01, 0x23, 0xd4, 0xb1, 0x08, 0xb5, 0x6d, 0xee, 0x49, 0x88, + 0x50, 0xb7, 0xd7, 0x53, 0xb8, 0xcd, 0x38, 0x04, 0xcf, 0xaa, 0x29, 0xcf, 0xe8, 0xd0, 0xeb, 0x73, + 0xd7, 0xfa, 0x2a, 0x42, 0x43, 0x6b, 0xc1, 0x0b, 0x1f, 0xf9, 0x44, 0x9b, 0xcc, 0x33, 0xd4, 0x53, + 0x83, 0x3d, 0x18, 0x32, 0xe1, 0xe1, 0x5b, 0xb0, 0x75, 0xc2, 0xc6, 0x05, 0x54, 0x46, 0x95, 0x8b, + 0xf5, 0x97, 0xf5, 0x64, 0xcd, 0x7a, 0x88, 0xba, 0xc7, 0xc6, 0x86, 0xff, 0x5e, 0xeb, 0x42, 0x61, + 0x39, 0xa2, 0x70, 0xb8, 0x2d, 0x18, 0x6e, 0xc2, 0x4e, 0x88, 0x55, 0x71, 0xaf, 0x67, 0xc5, 0x7d, + 0xcf, 0xf6, 0xdc, 0x71, 0xe3, 0xc2, 0xa3, 0x7f, 0xae, 0x6e, 0x18, 0x33, 0xb0, 0xf6, 0x2d, 0x82, + 0xc3, 0x85, 0x2c, 0x16, 0x13, 0x21, 0xf3, 0x6b, 0xb0, 0x47, 0x85, 0x60, 0x5e, 0xbb, 0x3b, 0xa0, + 0x42, 0xb4, 0xad, 0x9e, 0x4c, 0xf6, 0x8c, 0xf1, 0xac, 0x3c, 0xbd, 0xe3, 0x1f, 0xde, 0xed, 0xe1, + 0xf7, 0x01, 0xe6, 0x55, 0x29, 0x74, 0x25, 0x9d, 0x57, 0xf4, 0xa0, 0x84, 0xba, 0x5f, 0x42, 0x3d, + 0xa8, 0xb9, 0x2a, 0xa1, 0xde, 0xa2, 0x26, 0x53, 0x19, 0x8c, 0x08, 0x52, 0xfb, 0x19, 0x41, 0x31, + 0x89, 0x8b, 0xd2, 0x7c, 0x0f, 0xc0, 0x9d, 0x9d, 0x16, 0x50, 0x79, 0x2b, 0xaf, 0xea, 0x08, 0x1c, + 0x37, 0x13, 0x38, 0xbf, 0x9a, 0xc9, 0x39, 0x60, 0x12, 0x23, 0xfd, 0x10, 0xc1, 0x65, 0x49, 0xfa, + 0x03, 0x2a, 0x0c, 0x3e, 0x60, 0xe7, 0x2b, 0x3a, 0x2e, 0xc0, 0x36, 0xed, 0xf5, 0x5c, 0x26, 0x44, + 0x61, 0x53, 0x5a, 0x1d, 0x7e, 0xe2, 0xdb, 0x70, 0xc1, 0xe5, 0x03, 0x56, 0xd8, 0x2a, 0xa3, 0xca, + 0x5e, 0xfd, 0x5a, 0x56, 0x44, 0xc9, 0x45, 0x22, 0xb4, 0x1a, 0x1c, 0xc4, 0x19, 0x2a, 0x43, 0x0f, + 0x61, 0xa7, 0x4f, 0x45, 0x5b, 0x46, 0xf5, 0x79, 0xee, 0x18, 0xdb, 0xfd, 0xe0, 0x89, 0x46, 0xe0, + 0x25, 0x09, 0x69, 0x31, 0xbb, 0x67, 0xd9, 0xa6, 0x7f, 0x76, 0xa7, 0x4f, 0xed, 0x59, 0xdd, 0xf0, + 0x1e, 0x6c, 0xce, 0xba, 0x61, 0xd3, 0xea, 0x69, 0x5f, 0x23, 0x28, 0xa5, 0x21, 0x54, 0xba, 0x36, + 0x5c, 0x76, 0x82, 0x4b, 0x99, 0xb2, 0xdd, 0x95, 0xd7, 0xca, 0xa1, 0xd7, 0xd2, 0xf4, 0x2c, 0xc5, + 0x53, 0xc5, 0xdc, 0x77, 0x16, 0x2f, 0xb4, 0x1f, 0x53, 0x39, 0x88, 0x73, 0x56, 0x65, 0x5d, 0x1d, + 0xfe, 0x27, 0x82, 0xab, 0xa9, 0x0c, 0x95, 0x4d, 0x14, 0x0e, 0x12, 0x6c, 0x0a, 0x1b, 0x3e, 0xb7, + 0x4f, 0x78, 0xc9, 0xa7, 0x35, 0x36, 0x7f, 0x53, 0x0d, 0x8f, 0xd0, 0x30, 0x39, 0x11, 0x42, 0xaf, + 0xab, 0xb0, 0x1f, 0x12, 0x5c, 0x9c, 0x1f, 0x97, 0xdc, 0x28, 0xe0, 0x6e, 0x4f, 0x73, 0xd4, 0x5f, + 0xfe, 0x42, 0x20, 0x65, 0x89, 0x01, 0x7b, 0xf1, 0x48, 0xab, 0xce, 0x3c, 0x19, 0x46, 0x19, 0xb1, + 0x1b, 0xcb, 0xa9, 0x31, 0x78, 0x71, 0x39, 0xe3, 0xbc, 0x51, 0xd6, 0x55, 0xf1, 0xdf, 0x11, 0x5c, + 0x49, 0xce, 0xa3, 0xb4, 0x7d, 0x0a, 0xcf, 0xc5, 0xb5, 0xad, 0x3e, 0xdb, 0xa2, 0xea, 0xe2, 0x8e, + 0xae, 0xb1, 0xc6, 0xf5, 0x1f, 0x00, 0x9e, 0x92, 0x0a, 0xf0, 0x6f, 0x08, 0x2e, 0x46, 0x96, 0x11, + 0x26, 0x69, 0x04, 0x53, 0x16, 0x61, 0xf1, 0x68, 0x75, 0x40, 0x40, 0x44, 0xfb, 0xf0, 0x9b, 0xbf, + 0xfe, 0xfb, 0x7e, 0xf3, 0x5d, 0xdc, 0x20, 0x19, 0x1b, 0x9b, 0x4c, 0x4e, 0xd8, 0x58, 0x8f, 0x2f, + 0xab, 0x69, 0x70, 0x68, 0x7f, 0xe1, 0xf9, 0x1f, 0xf8, 0x21, 0x82, 0xdd, 0xd8, 0x66, 0xc1, 0xb5, + 0x15, 0xf9, 0xcc, 0x37, 0x62, 0xb1, 0x9e, 0x07, 0xa2, 0x44, 0x54, 0xa4, 0x08, 0x0d, 0x97, 0xb3, + 0x44, 0xe0, 0x3f, 0x10, 0x6c, 0xab, 0x29, 0x8d, 0x5f, 0x7f, 0x62, 0xa6, 0xf8, 0xb6, 0x29, 0xde, + 0x58, 0xed, 0xb1, 0x22, 0xf4, 0xb9, 0x24, 0xf4, 0x09, 0xfe, 0x38, 0x8d, 0x50, 0xb8, 0x16, 0xb2, + 0x5d, 0x25, 0x13, 0xb5, 0x9f, 0xa6, 0x64, 0xe2, 0x23, 0xa6, 0x7e, 0x97, 0xec, 0x2f, 0x0d, 0x23, + 0x7c, 0xeb, 0x89, 0x04, 0xd3, 0xd6, 0x4c, 0xf1, 0xcd, 0xbc, 0x30, 0xa5, 0xf0, 0xb6, 0x54, 0x58, + 0xc7, 0x47, 0x69, 0x0a, 0x13, 0x46, 0x2c, 0x99, 0xf8, 0x5d, 0xf2, 0x2b, 0x02, 0xbc, 0x3c, 0x9d, + 0x71, 0x4e, 0x22, 0xb3, 0x7e, 0x79, 0x2b, 0x37, 0x4e, 0x29, 0x38, 0x96, 0x0a, 0x74, 0x7c, 0x23, + 0x87, 0x02, 0x81, 0x7f, 0x41, 0xb0, 0x1b, 0x1b, 0x0f, 0x19, 0x3d, 0x9e, 0x34, 0xb8, 0x33, 0x7a, + 0x3c, 0x71, 0x44, 0x6b, 0x0d, 0x49, 0xf7, 0x1d, 0xfc, 0x76, 0x56, 0x8f, 0x07, 0x7d, 0x44, 0x26, + 0x4b, 0xab, 0x61, 0x8a, 0x7f, 0x42, 0x70, 0x69, 0x61, 0x4c, 0xe2, 0x9b, 0xab, 0x73, 0x99, 0x9b, + 0x7e, 0x9c, 0x0f, 0xa4, 0x24, 0x1c, 0x49, 0x09, 0x55, 0x5c, 0x59, 0x4d, 0x02, 0x13, 0x8d, 0x93, + 0x47, 0xa7, 0x25, 0xf4, 0xf8, 0xb4, 0x84, 0xfe, 0x3d, 0x2d, 0xa1, 0xef, 0xce, 0x4a, 0x1b, 0x8f, + 0xcf, 0x4a, 0x1b, 0x7f, 0x9f, 0x95, 0x36, 0xe0, 0xd0, 0xe2, 0x29, 0x1c, 0x5a, 0xe8, 0xb3, 0x63, + 0xd3, 0xf2, 0xfa, 0xc3, 0x8e, 0xde, 0xe5, 0xf7, 0x23, 0xa9, 0xde, 0xb0, 0x78, 0x34, 0xf1, 0x97, + 0xf3, 0xd4, 0xde, 0xd8, 0x61, 0xa2, 0xf3, 0xb4, 0xfc, 0x3f, 0xe3, 0xe6, 0xff, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x6b, 0xed, 0xbf, 0x4c, 0x58, 0x0d, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -653,6 +860,10 @@ type QueryClient interface { PendingRoleChange(ctx context.Context, in *QueryPendingRoleChangeRequest, opts ...grpc.CallOption) (*QueryPendingRoleChangeResponse, error) // PendingRoleChanges returns the pending role changes, optionally filtered by registry key. PendingRoleChanges(ctx context.Context, in *QueryPendingRoleChangesRequest, opts ...grpc.CallOption) (*QueryPendingRoleChangesResponse, error) + // RegistryClass returns a single registry class (including its authorization policy) by id. + RegistryClass(ctx context.Context, in *QueryRegistryClassRequest, opts ...grpc.CallOption) (*QueryRegistryClassResponse, error) + // RegistryClasses returns all registry classes. + RegistryClasses(ctx context.Context, in *QueryRegistryClassesRequest, opts ...grpc.CallOption) (*QueryRegistryClassesResponse, error) } type queryClient struct { @@ -708,6 +919,24 @@ func (c *queryClient) PendingRoleChanges(ctx context.Context, in *QueryPendingRo return out, nil } +func (c *queryClient) RegistryClass(ctx context.Context, in *QueryRegistryClassRequest, opts ...grpc.CallOption) (*QueryRegistryClassResponse, error) { + out := new(QueryRegistryClassResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Query/RegistryClass", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) RegistryClasses(ctx context.Context, in *QueryRegistryClassesRequest, opts ...grpc.CallOption) (*QueryRegistryClassesResponse, error) { + out := new(QueryRegistryClassesResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Query/RegistryClasses", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // GetRegistry returns the registry entry for a given key. @@ -722,6 +951,10 @@ type QueryServer interface { PendingRoleChange(context.Context, *QueryPendingRoleChangeRequest) (*QueryPendingRoleChangeResponse, error) // PendingRoleChanges returns the pending role changes, optionally filtered by registry key. PendingRoleChanges(context.Context, *QueryPendingRoleChangesRequest) (*QueryPendingRoleChangesResponse, error) + // RegistryClass returns a single registry class (including its authorization policy) by id. + RegistryClass(context.Context, *QueryRegistryClassRequest) (*QueryRegistryClassResponse, error) + // RegistryClasses returns all registry classes. + RegistryClasses(context.Context, *QueryRegistryClassesRequest) (*QueryRegistryClassesResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -743,6 +976,12 @@ func (*UnimplementedQueryServer) PendingRoleChange(ctx context.Context, req *Que func (*UnimplementedQueryServer) PendingRoleChanges(ctx context.Context, req *QueryPendingRoleChangesRequest) (*QueryPendingRoleChangesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method PendingRoleChanges not implemented") } +func (*UnimplementedQueryServer) RegistryClass(ctx context.Context, req *QueryRegistryClassRequest) (*QueryRegistryClassResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegistryClass not implemented") +} +func (*UnimplementedQueryServer) RegistryClasses(ctx context.Context, req *QueryRegistryClassesRequest) (*QueryRegistryClassesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegistryClasses not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -838,6 +1077,42 @@ func _Query_PendingRoleChanges_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _Query_RegistryClass_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRegistryClassRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).RegistryClass(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Query/RegistryClass", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).RegistryClass(ctx, req.(*QueryRegistryClassRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_RegistryClasses_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryRegistryClassesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).RegistryClasses(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Query/RegistryClasses", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).RegistryClasses(ctx, req.(*QueryRegistryClassesRequest)) + } + return interceptor(ctx, in, info, handler) +} + var Query_serviceDesc = _Query_serviceDesc var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Query", @@ -863,6 +1138,14 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "PendingRoleChanges", Handler: _Query_PendingRoleChanges_Handler, }, + { + MethodName: "RegistryClass", + Handler: _Query_RegistryClass_Handler, + }, + { + MethodName: "RegistryClasses", + Handler: _Query_RegistryClasses_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/query.proto", @@ -1274,84 +1557,235 @@ func (m *QueryPendingRoleChangesResponse) MarshalToSizedBuffer(dAtA []byte) (int return len(dAtA) - i, nil } -func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { - offset -= sovQuery(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *QueryRegistryClassRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *QueryGetRegistryRequest) Size() (n int) { - if m == nil { - return 0 - } + +func (m *QueryRegistryClassRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRegistryClassRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if m.Key != nil { - l = m.Key.Size() - n += 1 + l + sovQuery(uint64(l)) + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintQuery(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0xa } - return n + return len(dAtA) - i, nil } -func (m *QueryGetRegistryResponse) Size() (n int) { - if m == nil { - return 0 +func (m *QueryRegistryClassResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - l = m.Registry.Size() - n += 1 + l + sovQuery(uint64(l)) - return n + return dAtA[:n], nil } -func (m *QueryGetRegistriesRequest) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.AssetClassId) - if l > 0 { - n += 1 + l + sovQuery(uint64(l)) - } - if m.Pagination != nil { - l = m.Pagination.Size() - n += 2 + l + sovQuery(uint64(l)) - } - return n +func (m *QueryRegistryClassResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *QueryGetRegistriesResponse) Size() (n int) { - if m == nil { - return 0 - } +func (m *QueryRegistryClassResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - if len(m.Registries) > 0 { - for _, e := range m.Registries { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) + { + size, err := m.RegistryClass.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) } - if m.Pagination != nil { - l = m.Pagination.Size() - n += 2 + l + sovQuery(uint64(l)) - } - return n + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil } -func (m *QueryHasRoleRequest) Size() (n int) { - if m == nil { - return 0 +func (m *QueryRegistryClassesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - if m.Key != nil { + return dAtA[:n], nil +} + +func (m *QueryRegistryClassesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRegistryClassesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6 + i-- + dAtA[i] = 0x9a + } + return len(dAtA) - i, nil +} + +func (m *QueryRegistryClassesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryRegistryClassesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryRegistryClassesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x6 + i-- + dAtA[i] = 0x9a + } + if len(m.RegistryClasses) > 0 { + for iNdEx := len(m.RegistryClasses) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RegistryClasses[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryGetRegistryRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryGetRegistryResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Registry.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryGetRegistriesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryGetRegistriesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Registries) > 0 { + for _, e := range m.Registries { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryHasRoleRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Key != nil { l = m.Key.Size() n += 1 + l + sovQuery(uint64(l)) } @@ -1437,6 +1871,62 @@ func (m *QueryPendingRoleChangesResponse) Size() (n int) { return n } +func (m *QueryRegistryClassRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryRegistryClassResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.RegistryClass.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *QueryRegistryClassesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryRegistryClassesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.RegistryClasses) > 0 { + for _, e := range m.RegistryClasses { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 2 + l + sovQuery(uint64(l)) + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2464,6 +2954,377 @@ func (m *QueryPendingRoleChangesResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryRegistryClassRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRegistryClassRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRegistryClassRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryRegistryClassResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRegistryClassResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRegistryClassResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClass", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.RegistryClass.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryRegistryClassesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRegistryClassesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRegistryClassesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 99: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryRegistryClassesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryRegistryClassesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryRegistryClassesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClasses", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClasses = append(m.RegistryClasses, RegistryClass{}) + if err := m.RegistryClasses[len(m.RegistryClasses)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 99: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/query.pb.gw.go b/x/registry/types/query.pb.gw.go index 9a048cdd5f..589d756bde 100644 --- a/x/registry/types/query.pb.gw.go +++ b/x/registry/types/query.pb.gw.go @@ -397,6 +397,96 @@ func local_request_Query_PendingRoleChanges_0(ctx context.Context, marshaler run } +func request_Query_RegistryClass_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRegistryClassRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["registry_class_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "registry_class_id") + } + + protoReq.RegistryClassId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "registry_class_id", err) + } + + msg, err := client.RegistryClass(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_RegistryClass_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRegistryClassRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["registry_class_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "registry_class_id") + } + + protoReq.RegistryClassId, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "registry_class_id", err) + } + + msg, err := server.RegistryClass(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_RegistryClasses_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Query_RegistryClasses_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRegistryClassesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_RegistryClasses_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.RegistryClasses(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_RegistryClasses_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryRegistryClassesRequest + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_RegistryClasses_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.RegistryClasses(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -518,6 +608,52 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_RegistryClass_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_RegistryClass_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RegistryClass_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_RegistryClasses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_RegistryClasses_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RegistryClasses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -659,6 +795,46 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_RegistryClass_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_RegistryClass_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RegistryClass_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_RegistryClasses_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_RegistryClasses_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_RegistryClasses_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -672,6 +848,10 @@ var ( pattern_Query_PendingRoleChange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"provenance", "registry", "v1", "pending_role_change", "id"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_PendingRoleChanges_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "pending_role_changes"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_RegistryClass_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"provenance", "registry", "v1", "registry_class", "registry_class_id"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_RegistryClasses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "registry_classes"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -684,4 +864,8 @@ var ( forward_Query_PendingRoleChange_0 = runtime.ForwardResponseMessage forward_Query_PendingRoleChanges_0 = runtime.ForwardResponseMessage + + forward_Query_RegistryClass_0 = runtime.ForwardResponseMessage + + forward_Query_RegistryClasses_0 = runtime.ForwardResponseMessage ) diff --git a/x/registry/types/registry.pb.go b/x/registry/types/registry.pb.go index 9f1d39d846..58631bc8cb 100644 --- a/x/registry/types/registry.pb.go +++ b/x/registry/types/registry.pb.go @@ -168,6 +168,10 @@ type RegistryEntry struct { // roles is a list of roles and addresses that can perform that role. // Each role entry contains a role type and the addresses authorized for that role. Roles []RolesEntry `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles"` + // registry_class_id is the ID of the registry class governing this entry's authorization rules. + // If set, role updates for this entry use the authorization rules defined in the referenced + // registry class. If empty, role updates fall back to legacy NFT owner authorization. + RegistryClassId string `protobuf:"bytes,3,opt,name=registry_class_id,json=registryClassId,proto3" json:"registryClassId,omitempty"` } func (m *RegistryEntry) Reset() { *m = RegistryEntry{} } @@ -217,6 +221,13 @@ func (m *RegistryEntry) GetRoles() []RolesEntry { return nil } +func (m *RegistryEntry) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + // RolesEntry represents a mapping between a role and the addresses that can perform that role. // This allows multiple addresses to be assigned the same role for a given registry entry. type RolesEntry struct { @@ -431,47 +442,49 @@ func init() { } var fileDescriptor_2fa7a0b4d34d0208 = []byte{ - // 629 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x63, 0x27, 0x29, 0xcd, 0xa4, 0x54, 0x66, 0xd5, 0x16, 0xa7, 0x40, 0x5a, 0x85, 0x56, - 0x2a, 0x48, 0x4d, 0xd4, 0x52, 0x10, 0xe2, 0x80, 0x94, 0x3f, 0xdb, 0xca, 0x6a, 0x64, 0x47, 0x9b, - 0x84, 0xaa, 0x5c, 0x2c, 0x37, 0xde, 0xba, 0x56, 0x53, 0xaf, 0xe5, 0x75, 0x2b, 0x72, 0xe1, 0xc8, - 0x99, 0x0b, 0x12, 0x47, 0x1e, 0x82, 0x87, 0xe8, 0xb1, 0xe2, 0xc4, 0x09, 0xa1, 0xf6, 0xc8, 0x4b, - 0x20, 0xdb, 0x89, 0xd3, 0x34, 0x94, 0x88, 0x13, 0x37, 0xef, 0x7c, 0xbf, 0xd9, 0xf9, 0x66, 0xc6, - 0x5a, 0x58, 0x75, 0x3d, 0x76, 0x46, 0x1d, 0xc3, 0xe9, 0xd0, 0x92, 0x47, 0x2d, 0x9b, 0xfb, 0x5e, - 0xaf, 0x74, 0xb6, 0x11, 0x7f, 0x17, 0x5d, 0x8f, 0xf9, 0x0c, 0x2d, 0x0c, 0xb1, 0x62, 0x2c, 0x9d, - 0x6d, 0x2c, 0xce, 0x59, 0xcc, 0x62, 0x21, 0x52, 0x0a, 0xbe, 0x22, 0x7a, 0x31, 0xd7, 0x61, 0xfc, - 0x84, 0x71, 0x3d, 0x12, 0xa2, 0x43, 0x24, 0x15, 0x1a, 0x90, 0x25, 0xfd, 0xfc, 0x5d, 0xda, 0x43, - 0xf3, 0x30, 0xe5, 0x1c, 0xfa, 0xba, 0x6d, 0xca, 0xc2, 0xb2, 0xb0, 0x96, 0x21, 0x69, 0xe7, 0xd0, - 0x57, 0x4c, 0xb4, 0x02, 0xb3, 0x06, 0xe7, 0xd4, 0xd7, 0x3b, 0x5d, 0x83, 0xf3, 0x40, 0x16, 0x43, - 0x79, 0x26, 0x8c, 0x56, 0x83, 0xa0, 0x62, 0xbe, 0x4a, 0x7d, 0xfe, 0xb2, 0x94, 0x28, 0x7c, 0x10, - 0xe0, 0xee, 0xe0, 0x4a, 0xec, 0xf8, 0x5e, 0x0f, 0x3d, 0x87, 0xe4, 0x31, 0xed, 0x85, 0x37, 0x66, - 0x37, 0x1f, 0x17, 0xff, 0x6c, 0xbd, 0x78, 0xcd, 0x06, 0x09, 0x78, 0xf4, 0x1a, 0xd2, 0x1e, 0xeb, - 0x52, 0x2e, 0x8b, 0xcb, 0xc9, 0xb5, 0xec, 0x66, 0xe1, 0xd6, 0xc4, 0x00, 0x0a, 0x2b, 0x55, 0x52, - 0xe7, 0x3f, 0x96, 0x12, 0x24, 0x4a, 0x2b, 0xbc, 0x07, 0x18, 0x4a, 0xe8, 0x25, 0xa4, 0x82, 0x70, - 0xe8, 0x62, 0x76, 0x73, 0x65, 0x92, 0x8b, 0x20, 0x93, 0x84, 0x19, 0xe8, 0x05, 0x64, 0x0c, 0xd3, - 0xf4, 0x28, 0xe7, 0x7d, 0x2f, 0x99, 0x8a, 0xfc, 0xed, 0xeb, 0xfa, 0x5c, 0x7f, 0x8e, 0xe5, 0x48, - 0x6b, 0xfa, 0x9e, 0xed, 0x58, 0x64, 0x88, 0x0e, 0xea, 0xb7, 0x5d, 0xd3, 0xf0, 0xe9, 0x7f, 0xa8, - 0xff, 0x49, 0x84, 0x7b, 0x0d, 0xea, 0x98, 0x41, 0x98, 0x75, 0x69, 0xf5, 0xc8, 0x70, 0x2c, 0x8a, - 0x66, 0x41, 0x8c, 0xb7, 0x2b, 0xda, 0xe6, 0x60, 0x39, 0xe2, 0x3f, 0x2e, 0x67, 0x17, 0x66, 0x02, - 0x73, 0xfa, 0x69, 0xd8, 0x1d, 0x97, 0x93, 0x93, 0x77, 0x14, 0x0d, 0xa2, 0xbf, 0xa3, 0xac, 0x17, - 0x47, 0x38, 0xda, 0x82, 0x69, 0xd7, 0x63, 0x2e, 0xe3, 0xd4, 0x93, 0x53, 0x81, 0xb3, 0xbf, 0x34, - 0x18, 0x93, 0xe1, 0x5c, 0xdc, 0xa0, 0x9e, 0xd1, 0xe5, 0x72, 0x7a, 0xe2, 0x5c, 0x06, 0xe8, 0xd3, - 0x5f, 0x22, 0xcc, 0x5c, 0x1f, 0x33, 0x7a, 0x04, 0x39, 0x82, 0x77, 0x94, 0x66, 0x8b, 0xec, 0xeb, - 0x44, 0xab, 0x63, 0xbd, 0xad, 0x36, 0x1b, 0xb8, 0xaa, 0x6c, 0x2b, 0xb8, 0x26, 0x25, 0xd0, 0x22, - 0x2c, 0x8c, 0xca, 0x4d, 0x4c, 0xde, 0x28, 0x55, 0x4c, 0x24, 0x61, 0x3c, 0xb5, 0xd9, 0xae, 0xc4, - 0xb2, 0x88, 0x1e, 0x82, 0x3c, 0x2a, 0x57, 0x35, 0xb5, 0x45, 0xb4, 0x7a, 0x1d, 0x13, 0x29, 0x89, - 0x1e, 0xc0, 0xfd, 0x1b, 0x6a, 0xbb, 0xd9, 0xd2, 0x6a, 0x4a, 0x59, 0x95, 0x52, 0xe3, 0x55, 0x2b, - 0x1a, 0x21, 0xda, 0x1e, 0x26, 0x52, 0x7a, 0xfc, 0x5a, 0x8d, 0x28, 0x3b, 0x8a, 0x5a, 0x6e, 0x69, - 0x44, 0x9a, 0x1a, 0x57, 0xeb, 0x0a, 0x56, 0x75, 0x6d, 0x4f, 0xc5, 0x44, 0xba, 0x83, 0xd6, 0x60, - 0xe5, 0x66, 0x37, 0xd5, 0x36, 0xc1, 0x35, 0xbd, 0x51, 0x26, 0xad, 0x7d, 0x7d, 0x5b, 0x23, 0x21, - 0x2f, 0x4d, 0xa3, 0x27, 0xb0, 0x3a, 0x89, 0xc4, 0xaa, 0xd6, 0xc2, 0x52, 0x06, 0xe5, 0x60, 0x7e, - 0x14, 0x6d, 0xd4, 0x71, 0x6d, 0x07, 0x63, 0x09, 0x2a, 0xc7, 0xe7, 0x97, 0x79, 0xe1, 0xe2, 0x32, - 0x2f, 0xfc, 0xbc, 0xcc, 0x0b, 0x1f, 0xaf, 0xf2, 0x89, 0x8b, 0xab, 0x7c, 0xe2, 0xfb, 0x55, 0x3e, - 0x01, 0x39, 0x9b, 0xdd, 0xf2, 0xbb, 0x34, 0x84, 0xb7, 0x5b, 0x96, 0xed, 0x1f, 0x9d, 0x1e, 0x14, - 0x3b, 0xec, 0xa4, 0x34, 0x84, 0xd6, 0x6d, 0x76, 0xed, 0x54, 0x7a, 0x37, 0x7c, 0x22, 0xfd, 0x9e, - 0x4b, 0xf9, 0xc1, 0x54, 0xf8, 0xa8, 0x3d, 0xfb, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x01, 0x03, 0x85, - 0xeb, 0x46, 0x05, 0x00, 0x00, + // 662 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0xcd, 0x4e, 0xdb, 0x4c, + 0x14, 0x86, 0x63, 0x27, 0xe1, 0x23, 0x27, 0x7c, 0xd4, 0x8c, 0x80, 0x3a, 0xb4, 0x04, 0x94, 0x82, + 0x44, 0xab, 0x92, 0x08, 0x4a, 0xab, 0xaa, 0x8b, 0x4a, 0xf9, 0x19, 0x90, 0x45, 0x64, 0x47, 0x93, + 0xa4, 0x88, 0x6e, 0x2c, 0x13, 0x0f, 0xc1, 0x22, 0xf1, 0x58, 0x1e, 0x83, 0x9a, 0x4d, 0xaf, 0xa1, + 0x9b, 0x4a, 0x5d, 0xf6, 0x22, 0x7a, 0x11, 0x2c, 0x69, 0x57, 0x5d, 0xa1, 0x0a, 0x76, 0xed, 0x4d, + 0x54, 0xb6, 0x93, 0x18, 0x48, 0x51, 0xd4, 0x55, 0x77, 0xf6, 0x79, 0x9f, 0x33, 0xef, 0xf9, 0x19, + 0x0d, 0xac, 0x3a, 0x2e, 0x3b, 0xa5, 0xb6, 0x61, 0xb7, 0x68, 0xc1, 0xa5, 0x6d, 0x8b, 0x7b, 0x6e, + 0xaf, 0x70, 0xba, 0x31, 0xfc, 0xce, 0x3b, 0x2e, 0xf3, 0x18, 0x9a, 0x8f, 0xb0, 0xfc, 0x50, 0x3a, + 0xdd, 0x58, 0x98, 0x6d, 0xb3, 0x36, 0x0b, 0x90, 0x82, 0xff, 0x15, 0xd2, 0x0b, 0x99, 0x16, 0xe3, + 0x5d, 0xc6, 0xf5, 0x50, 0x08, 0x7f, 0x42, 0x29, 0x57, 0x83, 0x34, 0xe9, 0xe7, 0xef, 0xd2, 0x1e, + 0x9a, 0x83, 0x09, 0xfb, 0xd0, 0xd3, 0x2d, 0x53, 0x16, 0x96, 0x85, 0xb5, 0x14, 0x49, 0xda, 0x87, + 0x9e, 0x62, 0xa2, 0x15, 0x98, 0x36, 0x38, 0xa7, 0x9e, 0xde, 0xea, 0x18, 0x9c, 0xfb, 0xb2, 0x18, + 0xc8, 0x53, 0x41, 0xb4, 0xec, 0x07, 0x15, 0xf3, 0x55, 0xe2, 0xd3, 0xe7, 0xa5, 0x58, 0xee, 0xab, + 0x00, 0xff, 0x0f, 0x8e, 0xc4, 0xb6, 0xe7, 0xf6, 0xd0, 0x73, 0x88, 0x1f, 0xd3, 0x5e, 0x70, 0x62, + 0x7a, 0xf3, 0x51, 0xfe, 0xcf, 0xa5, 0xe7, 0xaf, 0x95, 0x41, 0x7c, 0x1e, 0xbd, 0x86, 0xa4, 0xcb, + 0x3a, 0x94, 0xcb, 0xe2, 0x72, 0x7c, 0x2d, 0xbd, 0x99, 0xbb, 0x33, 0xd1, 0x87, 0x02, 0xa7, 0x52, + 0xe2, 0xec, 0x62, 0x29, 0x46, 0xc2, 0x34, 0xa4, 0xc0, 0xcc, 0x00, 0x8b, 0xea, 0x8e, 0xfb, 0x75, + 0x97, 0x16, 0x7f, 0x5e, 0x2c, 0x65, 0x06, 0x62, 0xbf, 0xfc, 0xa7, 0xac, 0x6b, 0x79, 0xb4, 0xeb, + 0x78, 0x3d, 0x72, 0xef, 0x96, 0x94, 0x7b, 0x0f, 0x10, 0xb9, 0xa0, 0x97, 0x90, 0xf0, 0x1d, 0x82, + 0x86, 0xa6, 0x37, 0x57, 0xc6, 0x35, 0xe4, 0x67, 0x92, 0x20, 0x03, 0xbd, 0x80, 0x94, 0x61, 0x9a, + 0x2e, 0xe5, 0xbc, 0xdf, 0x56, 0xaa, 0x24, 0x7f, 0xfb, 0xb2, 0x3e, 0xdb, 0x5f, 0x49, 0x31, 0xd4, + 0xea, 0x9e, 0x6b, 0xd9, 0x6d, 0x12, 0xa1, 0x03, 0xff, 0xa6, 0x63, 0x1a, 0x1e, 0xfd, 0x07, 0xfe, + 0x1f, 0x45, 0x98, 0xa9, 0x51, 0xdb, 0xf4, 0xc3, 0xac, 0x43, 0xcb, 0x47, 0x86, 0xdd, 0xa6, 0x68, + 0x1a, 0xc4, 0xe1, 0x45, 0x11, 0x2d, 0x73, 0xb0, 0x67, 0xf1, 0x2f, 0xf7, 0xbc, 0x0b, 0x53, 0x7e, + 0x71, 0xfa, 0x49, 0xd0, 0x1d, 0x97, 0xe3, 0xe3, 0xd7, 0x1d, 0x0e, 0xa2, 0xbf, 0xee, 0xb4, 0x3b, + 0x8c, 0x70, 0xb4, 0x05, 0x93, 0x8e, 0xcb, 0x1c, 0xc6, 0xa9, 0x2b, 0x27, 0x82, 0x5d, 0xdf, 0xdd, + 0xe0, 0x90, 0x0c, 0xe6, 0xe2, 0xf8, 0x7e, 0x46, 0x87, 0xcb, 0xc9, 0xb1, 0x73, 0x19, 0xa0, 0x4f, + 0x7e, 0x89, 0x30, 0x75, 0x7d, 0xcc, 0x68, 0x11, 0x32, 0x04, 0xef, 0x28, 0xf5, 0x06, 0xd9, 0xd7, + 0x89, 0x56, 0xc5, 0x7a, 0x53, 0xad, 0xd7, 0x70, 0x59, 0xd9, 0x56, 0x70, 0x45, 0x8a, 0xa1, 0x05, + 0x98, 0xbf, 0x29, 0xd7, 0x31, 0x79, 0xa3, 0x94, 0x31, 0x91, 0x84, 0xd1, 0xd4, 0x7a, 0xb3, 0x34, + 0x94, 0x45, 0xf4, 0x10, 0xe4, 0x9b, 0x72, 0x59, 0x53, 0x1b, 0x44, 0xab, 0x56, 0x31, 0x91, 0xe2, + 0xe8, 0x01, 0xdc, 0xbf, 0xa5, 0x36, 0xeb, 0x0d, 0xad, 0xa2, 0x14, 0x55, 0x29, 0x31, 0xea, 0x5a, + 0xd2, 0x08, 0xd1, 0xf6, 0x30, 0x91, 0x92, 0xa3, 0xc7, 0x6a, 0x44, 0xd9, 0x51, 0xd4, 0x62, 0x43, + 0x23, 0xd2, 0xc4, 0xa8, 0x5a, 0x55, 0xb0, 0xaa, 0x6b, 0x7b, 0x2a, 0x26, 0xd2, 0x7f, 0x68, 0x0d, + 0x56, 0x6e, 0x77, 0x53, 0x6e, 0x12, 0x5c, 0xd1, 0x6b, 0x45, 0xd2, 0xd8, 0xd7, 0xb7, 0x35, 0x12, + 0xf0, 0xd2, 0x24, 0x7a, 0x0c, 0xab, 0xe3, 0x48, 0xac, 0x6a, 0x0d, 0x2c, 0xa5, 0x50, 0x06, 0xe6, + 0x6e, 0xa2, 0xb5, 0x2a, 0xae, 0xec, 0x60, 0x2c, 0x41, 0xe9, 0xf8, 0xec, 0x32, 0x2b, 0x9c, 0x5f, + 0x66, 0x85, 0x1f, 0x97, 0x59, 0xe1, 0xc3, 0x55, 0x36, 0x76, 0x7e, 0x95, 0x8d, 0x7d, 0xbf, 0xca, + 0xc6, 0x20, 0x63, 0xb1, 0x3b, 0xae, 0x4b, 0x4d, 0x78, 0xbb, 0xd5, 0xb6, 0xbc, 0xa3, 0x93, 0x83, + 0x7c, 0x8b, 0x75, 0x0b, 0x11, 0xb4, 0x6e, 0xb1, 0x6b, 0x7f, 0x85, 0x77, 0xd1, 0x6b, 0xeb, 0xf5, + 0x1c, 0xca, 0x0f, 0x26, 0x82, 0xf7, 0xf1, 0xd9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xea, 0xe0, + 0xf7, 0xfe, 0x91, 0x05, 0x00, 0x00, } func (m *RegistryKey) Marshal() (dAtA []byte, err error) { @@ -531,6 +544,13 @@ func (m *RegistryEntry) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintRegistry(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0x1a + } if len(m.Roles) > 0 { for iNdEx := len(m.Roles) - 1; iNdEx >= 0; iNdEx-- { { @@ -750,6 +770,10 @@ func (m *RegistryEntry) Size() (n int) { n += 1 + l + sovRegistry(uint64(l)) } } + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovRegistry(uint64(l)) + } return n } @@ -1041,6 +1065,38 @@ func (m *RegistryEntry) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRegistry(dAtA[iNdEx:]) diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 852dba5833..83f379628f 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -42,6 +42,9 @@ type MsgRegisterNFT struct { // roles is a list of roles and addresses that can perform that role. // Each role entry defines a role type and the addresses authorized for that role. Roles []RolesEntry `protobuf:"bytes,3,rep,name=roles,proto3" json:"roles"` + // registry_class_id optionally associates the new registry entry with a registry class whose + // authorization rules govern subsequent role updates. If set, the referenced class must exist. + RegistryClassId string `protobuf:"bytes,4,opt,name=registry_class_id,json=registryClassId,proto3" json:"registryClassId,omitempty"` } func (m *MsgRegisterNFT) Reset() { *m = MsgRegisterNFT{} } @@ -98,6 +101,13 @@ func (m *MsgRegisterNFT) GetRoles() []RolesEntry { return nil } +func (m *MsgRegisterNFT) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + // MsgRegisterNFTResponse defines the response for RegisterNFT. // This is an empty response indicating successful registration. type MsgRegisterNFTResponse struct { @@ -895,6 +905,237 @@ func (m *MsgApproveRoleChangeResponse) GetApplied() bool { return false } +// MsgCreateRegistryClass creates a new registry class defining asset class-level authorization +// rules for role updates within an NFT collection. +type MsgCreateRegistryClass struct { + // signer must match the maintainer address. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // registry_class_id is the unique identifier for the registry class. Must be unique across all + // registry classes. + RegistryClassId string `protobuf:"bytes,2,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` + // asset_class_id is the Scope Specification ID or NFT Class ID this registry class governs. + AssetClassId string `protobuf:"bytes,3,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` + // maintainer is the address authorized to manage this registry class. It is permanent and + // cannot be changed after creation. + Maintainer string `protobuf:"bytes,4,opt,name=maintainer,proto3" json:"maintainer,omitempty"` + // role_authorizations are the initial authorization rules for this asset class. + RoleAuthorizations []RoleAuthorization `protobuf:"bytes,5,rep,name=role_authorizations,json=roleAuthorizations,proto3" json:"role_authorizations"` +} + +func (m *MsgCreateRegistryClass) Reset() { *m = MsgCreateRegistryClass{} } +func (m *MsgCreateRegistryClass) String() string { return proto.CompactTextString(m) } +func (*MsgCreateRegistryClass) ProtoMessage() {} +func (*MsgCreateRegistryClass) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{16} +} +func (m *MsgCreateRegistryClass) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateRegistryClass) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateRegistryClass.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateRegistryClass) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateRegistryClass.Merge(m, src) +} +func (m *MsgCreateRegistryClass) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateRegistryClass) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateRegistryClass.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateRegistryClass proto.InternalMessageInfo + +func (m *MsgCreateRegistryClass) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgCreateRegistryClass) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +func (m *MsgCreateRegistryClass) GetAssetClassId() string { + if m != nil { + return m.AssetClassId + } + return "" +} + +func (m *MsgCreateRegistryClass) GetMaintainer() string { + if m != nil { + return m.Maintainer + } + return "" +} + +func (m *MsgCreateRegistryClass) GetRoleAuthorizations() []RoleAuthorization { + if m != nil { + return m.RoleAuthorizations + } + return nil +} + +// MsgCreateRegistryClassResponse defines the response for CreateRegistryClass. +type MsgCreateRegistryClassResponse struct { +} + +func (m *MsgCreateRegistryClassResponse) Reset() { *m = MsgCreateRegistryClassResponse{} } +func (m *MsgCreateRegistryClassResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCreateRegistryClassResponse) ProtoMessage() {} +func (*MsgCreateRegistryClassResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{17} +} +func (m *MsgCreateRegistryClassResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCreateRegistryClassResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCreateRegistryClassResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCreateRegistryClassResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCreateRegistryClassResponse.Merge(m, src) +} +func (m *MsgCreateRegistryClassResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCreateRegistryClassResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCreateRegistryClassResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCreateRegistryClassResponse proto.InternalMessageInfo + +// MsgUpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing +// registry class. The new rules completely replace the existing rules (not a merge). +type MsgUpdateRegistryClassRoleAuthorization struct { + // signer must be the current maintainer address. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // registry_class_id identifies the registry class to update. + RegistryClassId string `protobuf:"bytes,2,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` + // role_authorizations are the updated authorization rules (replaces existing rules). + RoleAuthorizations []RoleAuthorization `protobuf:"bytes,3,rep,name=role_authorizations,json=roleAuthorizations,proto3" json:"role_authorizations"` +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) Reset() { + *m = MsgUpdateRegistryClassRoleAuthorization{} +} +func (m *MsgUpdateRegistryClassRoleAuthorization) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateRegistryClassRoleAuthorization) ProtoMessage() {} +func (*MsgUpdateRegistryClassRoleAuthorization) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{18} +} +func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorization.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorization.Merge(m, src) +} +func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorization.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorization proto.InternalMessageInfo + +func (m *MsgUpdateRegistryClassRoleAuthorization) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) GetRoleAuthorizations() []RoleAuthorization { + if m != nil { + return m.RoleAuthorizations + } + return nil +} + +// MsgUpdateRegistryClassRoleAuthorizationResponse defines the response for +// UpdateRegistryClassRoleAuthorization. +type MsgUpdateRegistryClassRoleAuthorizationResponse struct { +} + +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) Reset() { + *m = MsgUpdateRegistryClassRoleAuthorizationResponse{} +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) String() string { + return proto.CompactTextString(m) +} +func (*MsgUpdateRegistryClassRoleAuthorizationResponse) ProtoMessage() {} +func (*MsgUpdateRegistryClassRoleAuthorizationResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{19} +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorizationResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorizationResponse.Merge(m, src) +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorizationResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorizationResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgRegisterNFT)(nil), "provenance.registry.v1.MsgRegisterNFT") proto.RegisterType((*MsgRegisterNFTResponse)(nil), "provenance.registry.v1.MsgRegisterNFTResponse") @@ -912,61 +1153,78 @@ func init() { proto.RegisterType((*MsgProposeRoleChangeResponse)(nil), "provenance.registry.v1.MsgProposeRoleChangeResponse") proto.RegisterType((*MsgApproveRoleChange)(nil), "provenance.registry.v1.MsgApproveRoleChange") proto.RegisterType((*MsgApproveRoleChangeResponse)(nil), "provenance.registry.v1.MsgApproveRoleChangeResponse") + proto.RegisterType((*MsgCreateRegistryClass)(nil), "provenance.registry.v1.MsgCreateRegistryClass") + proto.RegisterType((*MsgCreateRegistryClassResponse)(nil), "provenance.registry.v1.MsgCreateRegistryClassResponse") + proto.RegisterType((*MsgUpdateRegistryClassRoleAuthorization)(nil), "provenance.registry.v1.MsgUpdateRegistryClassRoleAuthorization") + proto.RegisterType((*MsgUpdateRegistryClassRoleAuthorizationResponse)(nil), "provenance.registry.v1.MsgUpdateRegistryClassRoleAuthorizationResponse") } func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 781 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x96, 0xcf, 0x4f, 0xdb, 0x48, - 0x14, 0xc7, 0x33, 0x84, 0x5f, 0x79, 0x01, 0xb4, 0xeb, 0xe5, 0x87, 0xf1, 0xee, 0x86, 0x28, 0xc0, - 0x2a, 0x62, 0x37, 0x09, 0x64, 0xa1, 0x42, 0x3d, 0x54, 0x22, 0x15, 0xad, 0x2a, 0x94, 0x0a, 0x99, - 0x72, 0xa9, 0x2a, 0x45, 0x26, 0x19, 0x19, 0x2b, 0xc4, 0x63, 0xcd, 0x38, 0x29, 0xe1, 0x54, 0xf5, - 0xd4, 0x63, 0xff, 0x81, 0xfe, 0x0f, 0x1c, 0xfa, 0x37, 0x54, 0x1c, 0x51, 0x4f, 0xed, 0xa5, 0xaa, - 0xe0, 0xc0, 0xb9, 0x97, 0xf6, 0x5a, 0x79, 0xec, 0x4c, 0x6c, 0x92, 0x98, 0xd0, 0x4a, 0x54, 0x6a, - 0x6f, 0x1e, 0xcf, 0xe7, 0xcd, 0xfb, 0x7e, 0x9f, 0xe7, 0xcd, 0x18, 0xe6, 0x2c, 0x4a, 0x1a, 0xd8, - 0xd4, 0xcc, 0x32, 0xce, 0x51, 0xac, 0x1b, 0xcc, 0xa6, 0xcd, 0x5c, 0x63, 0x25, 0x67, 0x1f, 0x66, - 0x2d, 0x4a, 0x6c, 0x22, 0x4d, 0xb7, 0x81, 0x6c, 0x0b, 0xc8, 0x36, 0x56, 0x94, 0x49, 0x9d, 0xe8, - 0x84, 0x23, 0x39, 0xe7, 0xc9, 0xa5, 0x95, 0xd9, 0x32, 0x61, 0x35, 0xc2, 0x4a, 0xee, 0x84, 0x3b, - 0xf0, 0xa6, 0x66, 0xdc, 0x51, 0xae, 0xc6, 0x74, 0x27, 0x41, 0x8d, 0xe9, 0xde, 0xc4, 0x62, 0x0f, - 0x09, 0x22, 0x9b, 0x8b, 0x2d, 0xf5, 0xc0, 0xb4, 0xba, 0xbd, 0x4f, 0xa8, 0x71, 0xa4, 0xd9, 0x06, - 0x31, 0x5d, 0x36, 0xf5, 0x06, 0xc1, 0x44, 0x91, 0xe9, 0x2a, 0xc7, 0x30, 0x7d, 0x78, 0xef, 0x91, - 0xb4, 0x0c, 0xc3, 0xcc, 0xd0, 0x4d, 0x4c, 0x65, 0x94, 0x44, 0xe9, 0x58, 0x41, 0x7e, 0xfb, 0x3a, - 0x33, 0xe9, 0x09, 0xdc, 0xa8, 0x54, 0x28, 0x66, 0x6c, 0xc7, 0xa6, 0x86, 0xa9, 0xab, 0x1e, 0x27, - 0xad, 0x41, 0xb4, 0x8a, 0x9b, 0xf2, 0x40, 0x12, 0xa5, 0xe3, 0xf9, 0xf9, 0x6c, 0xf7, 0x3a, 0x64, - 0x55, 0xef, 0x79, 0x0b, 0x37, 0x55, 0x87, 0x97, 0xee, 0xc0, 0x10, 0x25, 0x07, 0x98, 0xc9, 0xd1, - 0x64, 0x34, 0x1d, 0xcf, 0xa7, 0x7a, 0x06, 0x3a, 0xd0, 0xa6, 0x69, 0xd3, 0x66, 0x61, 0xf0, 0xe4, - 0xc3, 0x5c, 0x44, 0x75, 0xc3, 0x6e, 0xc7, 0x9f, 0x5f, 0x1c, 0x2f, 0x79, 0x1a, 0x52, 0x32, 0x4c, - 0x07, 0x7d, 0xa8, 0x98, 0x59, 0xc4, 0x64, 0x38, 0xf5, 0x19, 0xc1, 0x58, 0x91, 0xe9, 0xf7, 0xa9, - 0x66, 0xda, 0xce, 0x52, 0x37, 0x67, 0x70, 0x1d, 0x06, 0x1d, 0xa5, 0x72, 0x34, 0x89, 0xd2, 0x13, - 0xf9, 0x85, 0xab, 0xe2, 0x1c, 0x71, 0x2a, 0x8f, 0x90, 0x6e, 0x41, 0x4c, 0x73, 0x95, 0x60, 0x26, - 0x0f, 0x26, 0xa3, 0xa1, 0x2a, 0xdb, 0x68, 0xb0, 0x24, 0xd3, 0x30, 0xe9, 0xf7, 0x2d, 0x0a, 0xf2, - 0x05, 0xc1, 0x38, 0xaf, 0x55, 0x83, 0x54, 0xf1, 0x2f, 0x55, 0x91, 0x19, 0x98, 0x0a, 0x18, 0x17, - 0x25, 0x79, 0x81, 0xe0, 0xb7, 0x22, 0xd3, 0x77, 0x4d, 0xfa, 0x03, 0x1a, 0x21, 0xa8, 0x51, 0x01, - 0xf9, 0xb2, 0x12, 0x21, 0xf3, 0x15, 0xf2, 0x0c, 0xb8, 0x0b, 0x14, 0xea, 0x07, 0xd5, 0x5d, 0xab, - 0xa2, 0xd9, 0xdf, 0xf2, 0x05, 0x37, 0x61, 0x04, 0x9b, 0x36, 0x35, 0x30, 0x93, 0x07, 0x78, 0xff, - 0x2d, 0x5e, 0xa5, 0xd7, 0xdf, 0x82, 0xad, 0xd8, 0xa0, 0xf6, 0x39, 0xf8, 0xbb, 0xab, 0x3c, 0x61, - 0xe0, 0x14, 0x41, 0xbc, 0xc8, 0xf4, 0x1d, 0xcc, 0x77, 0x24, 0xbb, 0xb9, 0x8d, 0xb7, 0x05, 0x63, - 0xce, 0x36, 0x2a, 0xd5, 0xb9, 0x9e, 0xbe, 0x8e, 0x1c, 0x57, 0xba, 0xe7, 0x37, 0x4e, 0xc5, 0x9b, - 0x4b, 0x9e, 0xa7, 0xe0, 0x0f, 0x9f, 0x23, 0xe1, 0xf4, 0x3d, 0xe2, 0xdd, 0xb7, 0x4d, 0x89, 0x45, - 0x18, 0xdf, 0x6c, 0x77, 0xf7, 0x35, 0x53, 0xc7, 0x3f, 0x83, 0xe5, 0x5d, 0xf8, 0xab, 0x9b, 0xb5, - 0x96, 0x77, 0xe9, 0x4f, 0x88, 0x95, 0xf9, 0x9b, 0x92, 0x51, 0x71, 0x5d, 0xaa, 0xa3, 0xee, 0x8b, - 0x07, 0x15, 0x49, 0x86, 0x11, 0xcd, 0xb2, 0x0e, 0x0c, 0x5c, 0xe1, 0x8e, 0x46, 0xd5, 0xd6, 0x30, - 0x45, 0x79, 0xc5, 0x36, 0x2c, 0x2e, 0xf0, 0xbb, 0x2a, 0x16, 0x10, 0x30, 0x10, 0x14, 0x10, 0xb4, - 0xb2, 0xce, 0xad, 0x74, 0xe4, 0x14, 0x56, 0x7c, 0x6a, 0x51, 0x40, 0x6d, 0xfe, 0xd3, 0x30, 0x44, - 0x8b, 0x4c, 0x97, 0x30, 0xc4, 0xfd, 0xb7, 0xe7, 0x3f, 0xbd, 0xea, 0x1b, 0xbc, 0x9d, 0x94, 0x6c, - 0x7f, 0x9c, 0x10, 0x52, 0x82, 0x58, 0xfb, 0x06, 0x5b, 0x08, 0x09, 0x16, 0x94, 0xf2, 0x5f, 0x3f, - 0x94, 0x48, 0xb0, 0x07, 0xe0, 0xbb, 0x11, 0x16, 0x43, 0xe5, 0xb5, 0x30, 0x25, 0xd3, 0x17, 0x26, - 0x72, 0x54, 0x61, 0x3c, 0x78, 0xc4, 0xa6, 0x43, 0xe2, 0x03, 0xa4, 0xb2, 0xdc, 0x2f, 0x29, 0x92, - 0x1d, 0x81, 0xd4, 0xe5, 0xa0, 0xcc, 0x5c, 0x59, 0x77, 0x3f, 0xae, 0xac, 0x5d, 0x0b, 0x17, 0xb9, - 0x9f, 0xc0, 0xa8, 0x38, 0xe3, 0xe6, 0x43, 0x96, 0x68, 0x41, 0xca, 0xbf, 0x7d, 0x40, 0x62, 0xf5, - 0xa7, 0xf0, 0x7b, 0xe7, 0xb9, 0x12, 0xf6, 0xb5, 0x3b, 0x68, 0x65, 0xf5, 0x3a, 0xb4, 0x3f, 0x71, - 0x67, 0x7b, 0x86, 0x25, 0xee, 0xa0, 0x43, 0x13, 0xf7, 0x6c, 0x43, 0x65, 0xe8, 0xd9, 0xc5, 0xf1, - 0x12, 0x2a, 0x54, 0x4f, 0xce, 0x12, 0xe8, 0xf4, 0x2c, 0x81, 0x3e, 0x9e, 0x25, 0xd0, 0xcb, 0xf3, - 0x44, 0xe4, 0xf4, 0x3c, 0x11, 0x79, 0x77, 0x9e, 0x88, 0xc0, 0xac, 0x41, 0x7a, 0x2c, 0xbc, 0x8d, - 0x1e, 0xaf, 0xea, 0x86, 0xbd, 0x5f, 0xdf, 0xcb, 0x96, 0x49, 0x2d, 0xd7, 0x86, 0x32, 0x06, 0xf1, - 0x8d, 0x72, 0x87, 0xed, 0x7f, 0x65, 0xbb, 0x69, 0x61, 0xb6, 0x37, 0xcc, 0xff, 0x90, 0xff, 0xff, - 0x1a, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x25, 0x93, 0x3c, 0xf9, 0x0b, 0x00, 0x00, + // 978 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0x33, 0xb6, 0xd3, 0xc6, 0xcf, 0x69, 0xa0, 0x9b, 0x34, 0xdd, 0x2c, 0xd4, 0xb1, 0xdc, + 0x04, 0x4c, 0x68, 0xbc, 0x4d, 0x68, 0xab, 0x88, 0x03, 0x28, 0x89, 0x4a, 0x15, 0x55, 0x46, 0xd5, + 0x96, 0x5c, 0x10, 0x92, 0xd9, 0xd8, 0xa3, 0xcd, 0xca, 0xf1, 0xce, 0x6a, 0x66, 0x12, 0xea, 0x4a, + 0x48, 0x88, 0x13, 0x47, 0xf8, 0x00, 0x9c, 0xf8, 0x02, 0x39, 0xf0, 0x15, 0x90, 0x7a, 0x8c, 0x38, + 0xc1, 0xa5, 0x42, 0xc9, 0xa1, 0x12, 0x1f, 0x00, 0xae, 0x68, 0x67, 0x77, 0xc7, 0xbb, 0xf6, 0x7a, + 0xbd, 0x69, 0xd5, 0x20, 0xc1, 0xcd, 0xbb, 0xf3, 0x9b, 0x79, 0xff, 0xff, 0xf3, 0x9b, 0x79, 0xb3, + 0xb0, 0xe8, 0x52, 0x72, 0x84, 0x1d, 0xd3, 0x69, 0x61, 0x9d, 0x62, 0xcb, 0x66, 0x9c, 0xf6, 0xf4, + 0xa3, 0x35, 0x9d, 0x3f, 0xa9, 0xbb, 0x94, 0x70, 0xa2, 0xcc, 0xf7, 0x81, 0x7a, 0x08, 0xd4, 0x8f, + 0xd6, 0xb4, 0x39, 0x8b, 0x58, 0x44, 0x20, 0xba, 0xf7, 0xcb, 0xa7, 0xb5, 0x85, 0x16, 0x61, 0x5d, + 0xc2, 0x9a, 0xfe, 0x80, 0xff, 0x10, 0x0c, 0x5d, 0xf7, 0x9f, 0xf4, 0x2e, 0xb3, 0xbc, 0x00, 0x5d, + 0x66, 0x05, 0x03, 0xcb, 0x23, 0x24, 0xc8, 0x68, 0x3e, 0xb6, 0x32, 0x02, 0x33, 0x0f, 0xf9, 0x3e, + 0xa1, 0xf6, 0x53, 0x93, 0xdb, 0xc4, 0xf1, 0xd9, 0xea, 0x0f, 0x39, 0x98, 0x69, 0x30, 0xcb, 0x10, + 0x18, 0xa6, 0x9f, 0x7e, 0xf2, 0x99, 0x72, 0x1b, 0x2e, 0x31, 0xdb, 0x72, 0x30, 0x55, 0x51, 0x05, + 0xd5, 0x8a, 0x5b, 0xea, 0xaf, 0x3f, 0xaf, 0xce, 0x05, 0x02, 0x37, 0xdb, 0x6d, 0x8a, 0x19, 0x7b, + 0xcc, 0xa9, 0xed, 0x58, 0x46, 0xc0, 0x29, 0x77, 0x21, 0xdf, 0xc1, 0x3d, 0x35, 0x57, 0x41, 0xb5, + 0xd2, 0xfa, 0xcd, 0x7a, 0x72, 0x1e, 0xea, 0x46, 0xf0, 0xfb, 0x21, 0xee, 0x19, 0x1e, 0xaf, 0x7c, + 0x04, 0x93, 0x94, 0x1c, 0x60, 0xa6, 0xe6, 0x2b, 0xf9, 0x5a, 0x69, 0xbd, 0x3a, 0x72, 0xa2, 0x07, + 0xdd, 0x77, 0x38, 0xed, 0x6d, 0x15, 0x9e, 0x3d, 0x5f, 0x9c, 0x30, 0xfc, 0x69, 0xca, 0x0e, 0x5c, + 0x0d, 0xb1, 0x66, 0xeb, 0xc0, 0x64, 0xac, 0x69, 0xb7, 0xd5, 0x82, 0xd0, 0x7c, 0xe3, 0xcf, 0xe7, + 0x8b, 0x0b, 0xe1, 0xe0, 0xb6, 0x37, 0xb6, 0xd3, 0xbe, 0x45, 0xba, 0x36, 0xc7, 0x5d, 0x97, 0xf7, + 0x8c, 0x37, 0x06, 0x86, 0x3e, 0x2c, 0x7d, 0xfb, 0xe2, 0x78, 0x25, 0xb0, 0x53, 0x55, 0x61, 0x3e, + 0x9e, 0x12, 0x03, 0x33, 0x97, 0x38, 0x0c, 0x57, 0xff, 0x42, 0x30, 0xdd, 0x60, 0xd6, 0x03, 0x6a, + 0x3a, 0xdc, 0x53, 0x75, 0x71, 0xb9, 0xda, 0x80, 0x82, 0x67, 0x5a, 0xcd, 0x57, 0x50, 0x6d, 0x66, + 0x7d, 0x69, 0xdc, 0x3c, 0x4f, 0x9c, 0x21, 0x66, 0x28, 0xf7, 0xa0, 0x68, 0xfa, 0x4a, 0x30, 0x53, + 0x0b, 0x95, 0x7c, 0xaa, 0xca, 0x3e, 0x1a, 0x4f, 0xc9, 0x3c, 0xcc, 0x45, 0x7d, 0xcb, 0x84, 0xfc, + 0x8d, 0xe0, 0x8a, 0xc8, 0xd5, 0x11, 0xe9, 0xe0, 0xff, 0x55, 0x46, 0xae, 0xc3, 0xb5, 0x98, 0x71, + 0x99, 0x92, 0xef, 0x10, 0xbc, 0xd9, 0x60, 0xd6, 0xae, 0x43, 0xff, 0x85, 0x3d, 0x15, 0xd7, 0xa8, + 0x81, 0x3a, 0xa8, 0x44, 0xca, 0xfc, 0x11, 0x05, 0x06, 0xfc, 0x05, 0xb6, 0x0e, 0x0f, 0x3a, 0xbb, + 0x6e, 0xdb, 0xe4, 0x2f, 0xf3, 0x0f, 0xde, 0x87, 0xcb, 0xd8, 0xe1, 0xd4, 0xc6, 0x4c, 0xcd, 0x89, + 0xad, 0xbc, 0x3c, 0x4e, 0x6f, 0x74, 0x37, 0x87, 0x73, 0xe3, 0xda, 0x17, 0xe1, 0x46, 0xa2, 0x3c, + 0x69, 0xe0, 0x04, 0x41, 0xa9, 0xc1, 0xac, 0xc7, 0x58, 0x54, 0x24, 0xbb, 0xb8, 0xc2, 0x7b, 0x08, + 0xd3, 0x5e, 0x19, 0x35, 0x0f, 0x85, 0x9e, 0x4c, 0xa7, 0x97, 0x2f, 0x3d, 0xf0, 0x5b, 0xa2, 0xf2, + 0xcd, 0x80, 0xe7, 0x6b, 0x30, 0x1b, 0x71, 0x24, 0x9d, 0xfe, 0x8e, 0xc4, 0xee, 0x7b, 0x44, 0x89, + 0x4b, 0x98, 0x28, 0xb6, 0xed, 0x7d, 0xd3, 0xb1, 0xf0, 0x7f, 0xc1, 0xf2, 0x2e, 0xbc, 0x9d, 0x64, + 0x2d, 0xf4, 0xae, 0xbc, 0x05, 0xc5, 0x96, 0x78, 0xe3, 0x9d, 0xed, 0xc2, 0xa5, 0x31, 0xe5, 0xbf, + 0xd8, 0x69, 0x2b, 0x2a, 0x5c, 0x36, 0x5d, 0xf7, 0xc0, 0xc6, 0x6d, 0xe1, 0x68, 0xca, 0x08, 0x1f, + 0xab, 0x54, 0x64, 0x6c, 0xd3, 0x15, 0x02, 0x5f, 0x29, 0x63, 0x31, 0x01, 0xb9, 0xb8, 0x80, 0xb8, + 0x95, 0x0d, 0x61, 0x65, 0x28, 0xa6, 0xb4, 0x12, 0x51, 0x8b, 0xe2, 0x6a, 0x7f, 0xc9, 0x89, 0x8e, + 0xb3, 0x4d, 0xb1, 0x28, 0xf0, 0x48, 0x6b, 0x7a, 0x09, 0xc1, 0x2b, 0x49, 0x5d, 0xd1, 0x17, 0x3e, + 0xd8, 0xf6, 0x94, 0x25, 0x98, 0x31, 0x19, 0xc3, 0xbc, 0x0f, 0xe6, 0x05, 0x38, 0x2d, 0xde, 0x86, + 0xd4, 0x06, 0x40, 0xd7, 0xb4, 0x1d, 0x6e, 0xda, 0x9e, 0x8e, 0xc2, 0x18, 0x1d, 0x11, 0x56, 0xf9, + 0x12, 0x66, 0x45, 0xdd, 0xc4, 0x6e, 0x1e, 0x4c, 0x9d, 0x14, 0xe5, 0xf3, 0x5e, 0x5a, 0xf9, 0x6c, + 0x46, 0x67, 0x04, 0x55, 0xa4, 0xd0, 0xc1, 0x81, 0x81, 0x62, 0xaa, 0x40, 0x39, 0x39, 0x8d, 0xd1, + 0x06, 0xfe, 0xae, 0x77, 0x24, 0x06, 0x47, 0x49, 0x14, 0x19, 0x5c, 0xfb, 0x35, 0xa7, 0x7e, 0x44, + 0x6a, 0xf2, 0xaf, 0x29, 0x35, 0x6b, 0xa0, 0x67, 0xf4, 0x1d, 0xe6, 0x6a, 0xfd, 0xa7, 0x22, 0xe4, + 0x1b, 0xcc, 0x52, 0x30, 0x94, 0xa2, 0xd7, 0xc3, 0x77, 0x46, 0x69, 0x8b, 0xdf, 0x99, 0xb4, 0x7a, + 0x36, 0x4e, 0x6e, 0x8f, 0x26, 0x14, 0xfb, 0xf7, 0xaa, 0xa5, 0x94, 0xc9, 0x92, 0xd2, 0x6e, 0x65, + 0xa1, 0x64, 0x80, 0x3d, 0x80, 0xc8, 0x3d, 0x65, 0x39, 0x55, 0x5e, 0x88, 0x69, 0xab, 0x99, 0x30, + 0x19, 0xa3, 0x03, 0x57, 0xe2, 0x8d, 0xbf, 0x96, 0x32, 0x3f, 0x46, 0x6a, 0xb7, 0xb3, 0x92, 0x32, + 0xd8, 0x53, 0x50, 0x12, 0xda, 0xf7, 0xea, 0xd8, 0xbc, 0x47, 0x71, 0xed, 0xee, 0xb9, 0x70, 0x19, + 0xfb, 0x0b, 0x98, 0x92, 0x9d, 0xf7, 0x66, 0xca, 0x12, 0x21, 0xa4, 0xbd, 0x9f, 0x01, 0x92, 0xab, + 0x7f, 0x05, 0x57, 0x87, 0xbb, 0x5d, 0xda, 0xbf, 0x3d, 0x44, 0x6b, 0x77, 0xce, 0x43, 0x47, 0x03, + 0x0f, 0x37, 0x8d, 0xb4, 0xc0, 0x43, 0x74, 0x6a, 0xe0, 0xd1, 0xcd, 0xe1, 0x6b, 0x98, 0x4d, 0x3a, + 0xfe, 0xd3, 0x36, 0x51, 0x02, 0xaf, 0xdd, 0x3b, 0x1f, 0x2f, 0xc3, 0x1f, 0x23, 0x58, 0xca, 0x74, + 0x28, 0x7e, 0x9c, 0x56, 0xa5, 0x19, 0x16, 0xd0, 0x1e, 0xbc, 0xe2, 0x02, 0xa1, 0x64, 0x6d, 0xf2, + 0x9b, 0x17, 0xc7, 0x2b, 0x68, 0xab, 0xf3, 0xec, 0xb4, 0x8c, 0x4e, 0x4e, 0xcb, 0xe8, 0x8f, 0xd3, + 0x32, 0xfa, 0xfe, 0xac, 0x3c, 0x71, 0x72, 0x56, 0x9e, 0xf8, 0xed, 0xac, 0x3c, 0x01, 0x0b, 0x36, + 0x19, 0x11, 0xeb, 0x11, 0xfa, 0xfc, 0x8e, 0x65, 0xf3, 0xfd, 0xc3, 0xbd, 0x7a, 0x8b, 0x74, 0xf5, + 0x3e, 0xb4, 0x6a, 0x93, 0xc8, 0x93, 0xfe, 0xa4, 0xff, 0xf9, 0xcc, 0x7b, 0x2e, 0x66, 0x7b, 0x97, + 0xc4, 0x47, 0xf3, 0x07, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x12, 0x27, 0xcd, 0xcb, 0x0c, 0x10, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1011,6 +1269,12 @@ type MsgClient interface { // ApproveRoleChange records a single-signer approval for an open pending role change. // When the accumulated approvals satisfy the role's policy, the change is applied automatically. ApproveRoleChange(ctx context.Context, in *MsgApproveRoleChange, opts ...grpc.CallOption) (*MsgApproveRoleChangeResponse, error) + // CreateRegistryClass creates a new registry class that defines asset class-level authorization + // rules for role updates. The signer must match the maintainer address. + CreateRegistryClass(ctx context.Context, in *MsgCreateRegistryClass, opts ...grpc.CallOption) (*MsgCreateRegistryClassResponse, error) + // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry + // class. Only the current maintainer may update the rules. + UpdateRegistryClassRoleAuthorization(ctx context.Context, in *MsgUpdateRegistryClassRoleAuthorization, opts ...grpc.CallOption) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) } type msgClient struct { @@ -1093,6 +1357,24 @@ func (c *msgClient) ApproveRoleChange(ctx context.Context, in *MsgApproveRoleCha return out, nil } +func (c *msgClient) CreateRegistryClass(ctx context.Context, in *MsgCreateRegistryClass, opts ...grpc.CallOption) (*MsgCreateRegistryClassResponse, error) { + out := new(MsgCreateRegistryClassResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/CreateRegistryClass", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) UpdateRegistryClassRoleAuthorization(ctx context.Context, in *MsgUpdateRegistryClassRoleAuthorization, opts ...grpc.CallOption) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) { + out := new(MsgUpdateRegistryClassRoleAuthorizationResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/UpdateRegistryClassRoleAuthorization", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // RegisterNFT registers a new NFT in the registry. @@ -1125,6 +1407,12 @@ type MsgServer interface { // ApproveRoleChange records a single-signer approval for an open pending role change. // When the accumulated approvals satisfy the role's policy, the change is applied automatically. ApproveRoleChange(context.Context, *MsgApproveRoleChange) (*MsgApproveRoleChangeResponse, error) + // CreateRegistryClass creates a new registry class that defines asset class-level authorization + // rules for role updates. The signer must match the maintainer address. + CreateRegistryClass(context.Context, *MsgCreateRegistryClass) (*MsgCreateRegistryClassResponse, error) + // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry + // class. Only the current maintainer may update the rules. + UpdateRegistryClassRoleAuthorization(context.Context, *MsgUpdateRegistryClassRoleAuthorization) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -1155,6 +1443,12 @@ func (*UnimplementedMsgServer) ProposeRoleChange(ctx context.Context, req *MsgPr func (*UnimplementedMsgServer) ApproveRoleChange(ctx context.Context, req *MsgApproveRoleChange) (*MsgApproveRoleChangeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ApproveRoleChange not implemented") } +func (*UnimplementedMsgServer) CreateRegistryClass(ctx context.Context, req *MsgCreateRegistryClass) (*MsgCreateRegistryClassResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateRegistryClass not implemented") +} +func (*UnimplementedMsgServer) UpdateRegistryClassRoleAuthorization(ctx context.Context, req *MsgUpdateRegistryClassRoleAuthorization) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistryClassRoleAuthorization not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -1304,6 +1598,42 @@ func _Msg_ApproveRoleChange_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _Msg_CreateRegistryClass_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCreateRegistryClass) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CreateRegistryClass(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/CreateRegistryClass", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CreateRegistryClass(ctx, req.(*MsgCreateRegistryClass)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_UpdateRegistryClassRoleAuthorization_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateRegistryClassRoleAuthorization) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateRegistryClassRoleAuthorization(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/UpdateRegistryClassRoleAuthorization", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateRegistryClassRoleAuthorization(ctx, req.(*MsgUpdateRegistryClassRoleAuthorization)) + } + return interceptor(ctx, in, info, handler) +} + var Msg_serviceDesc = _Msg_serviceDesc var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Msg", @@ -1341,6 +1671,14 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "ApproveRoleChange", Handler: _Msg_ApproveRoleChange_Handler, }, + { + MethodName: "CreateRegistryClass", + Handler: _Msg_CreateRegistryClass_Handler, + }, + { + MethodName: "UpdateRegistryClassRoleAuthorization", + Handler: _Msg_UpdateRegistryClassRoleAuthorization_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/tx.proto", @@ -1366,6 +1704,13 @@ func (m *MsgRegisterNFT) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintTx(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0x22 + } if len(m.Roles) > 0 { for iNdEx := len(m.Roles) - 1; iNdEx >= 0; iNdEx-- { { @@ -1960,54 +2305,220 @@ func (m *MsgApproveRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } -func encodeVarintTx(dAtA []byte, offset int, v uint64) int { - offset -= sovTx(v) - base := offset - for v >= 1<<7 { - dAtA[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *MsgCreateRegistryClass) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - dAtA[offset] = uint8(v) - return base + return dAtA[:n], nil } -func (m *MsgRegisterNFT) Size() (n int) { - if m == nil { - return 0 - } + +func (m *MsgCreateRegistryClass) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateRegistryClass) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i var l int _ = l - l = len(m.Signer) - if l > 0 { - n += 1 + l + sovTx(uint64(l)) + if len(m.RoleAuthorizations) > 0 { + for iNdEx := len(m.RoleAuthorizations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleAuthorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } } - if m.Key != nil { - l = m.Key.Size() - n += 1 + l + sovTx(uint64(l)) + if len(m.Maintainer) > 0 { + i -= len(m.Maintainer) + copy(dAtA[i:], m.Maintainer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Maintainer))) + i-- + dAtA[i] = 0x22 } - if len(m.Roles) > 0 { - for _, e := range m.Roles { - l = e.Size() - n += 1 + l + sovTx(uint64(l)) - } + if len(m.AssetClassId) > 0 { + i -= len(m.AssetClassId) + copy(dAtA[i:], m.AssetClassId) + i = encodeVarintTx(dAtA, i, uint64(len(m.AssetClassId))) + i-- + dAtA[i] = 0x1a } - return n + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintTx(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil } -func (m *MsgRegisterNFTResponse) Size() (n int) { - if m == nil { - return 0 +func (m *MsgCreateRegistryClassResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err } - var l int - _ = l - return n + return dAtA[:n], nil } -func (m *MsgGrantRole) Size() (n int) { - if m == nil { - return 0 - } - var l int +func (m *MsgCreateRegistryClassResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCreateRegistryClassResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RoleAuthorizations) > 0 { + for iNdEx := len(m.RoleAuthorizations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleAuthorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintTx(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgRegisterNFT) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovTx(uint64(l)) + } + if len(m.Roles) > 0 { + for _, e := range m.Roles { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRegisterNFTResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgGrantRole) Size() (n int) { + if m == nil { + return 0 + } + var l int _ = l l = len(m.Signer) if l > 0 { @@ -2227,6 +2738,78 @@ func (m *MsgApproveRoleChangeResponse) Size() (n int) { return n } +func (m *MsgCreateRegistryClass) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Maintainer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if len(m.RoleAuthorizations) > 0 { + for _, e := range m.RoleAuthorizations { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgCreateRegistryClassResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgUpdateRegistryClassRoleAuthorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if len(m.RoleAuthorizations) > 0 { + for _, e := range m.RoleAuthorizations { + l = e.Size() + n += 1 + l + sovTx(uint64(l)) + } + } + return n +} + +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2364,6 +2947,38 @@ func (m *MsgRegisterNFT) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) @@ -3847,6 +4462,466 @@ func (m *MsgApproveRoleChangeResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgCreateRegistryClass) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateRegistryClass: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateRegistryClass: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Maintainer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Maintainer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleAuthorizations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleAuthorizations = append(m.RoleAuthorizations, RoleAuthorization{}) + if err := m.RoleAuthorizations[len(m.RoleAuthorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCreateRegistryClassResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCreateRegistryClassResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCreateRegistryClassResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateRegistryClassRoleAuthorization) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateRegistryClassRoleAuthorization: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateRegistryClassRoleAuthorization: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleAuthorizations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleAuthorizations = append(m.RoleAuthorizations, RoleAuthorization{}) + if err := m.RoleAuthorizations[len(m.RoleAuthorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateRegistryClassRoleAuthorizationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateRegistryClassRoleAuthorizationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From 87dca7d8cf22e6703176039a2cce101bccc6d742 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Wed, 24 Jun 2026 15:55:22 -0600 Subject: [PATCH 16/36] feat(registry): add governance-managed module params for default role policies Phase C2: introduce registry module Params holding the default role authorization policies, forming the middle tier of two-tier resolution (registry class policy -> module params default -> NFT-owner fallback). - proto: params.proto with Params{role_authorizations}; MsgUpdateParams (authority) + response and UpdateParams rpc; Params query + REST route; genesis params field; EventParamsUpdated - types: DefaultParams (empty by default, preserving legacy NFT-owner authorization), Params.Validate, MsgUpdateParams ValidateBasic and codec registration, genesis params validation - keeper: Params collections.Item + internal gov authority; GetParams/ SetParams/ValidateAuthority; resolver default tier now reads params; UpdateParams handler (authority-gated); genesis and query handlers - cli: update-params tx (governance) and params query - rename DefaultRoleAuthorizations to ControllerRoleAuthorizations: the CONTROLLER policy is a ticket example, not a chain default; the default params are empty so behavior matches main (NFT-owner authorization) - tests: params acceptance coverage (default empty, authority enforcement, params-driven default tier, genesis round-trip); accumulation tests now install the CONTROLLER policy explicitly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/events.proto | 3 + proto/provenance/registry/v1/genesis.proto | 4 + proto/provenance/registry/v1/params.proto | 18 + proto/provenance/registry/v1/query.proto | 16 + proto/provenance/registry/v1/tx.proto | 22 +- x/registry/client/cli/query.go | 27 + x/registry/client/cli/tx.go | 59 +- x/registry/keeper/class.go | 7 +- x/registry/keeper/genesis.go | 5 + x/registry/keeper/keeper.go | 15 + x/registry/keeper/keys.go | 1 + x/registry/keeper/msg_server.go | 15 + x/registry/keeper/params.go | 50 ++ x/registry/keeper/params_acceptance_test.go | 191 +++++++ x/registry/keeper/query_server.go | 11 + ...ole_change_accumulation_acceptance_test.go | 6 + x/registry/types/authorization.go | 15 +- x/registry/types/events.go | 5 + x/registry/types/events.pb.go | 179 +++++- x/registry/types/genesis.go | 8 +- x/registry/types/genesis.pb.go | 98 +++- x/registry/types/msgs.go | 15 + x/registry/types/msgs_test.go | 1 + x/registry/types/params.go | 27 + x/registry/types/params.pb.go | 338 ++++++++++++ x/registry/types/query.pb.go | 457 +++++++++++++-- x/registry/types/query.pb.gw.go | 65 +++ x/registry/types/tx.pb.go | 518 +++++++++++++++--- 28 files changed, 1989 insertions(+), 187 deletions(-) create mode 100644 proto/provenance/registry/v1/params.proto create mode 100644 x/registry/keeper/params.go create mode 100644 x/registry/keeper/params_acceptance_test.go create mode 100644 x/registry/types/params.go create mode 100644 x/registry/types/params.pb.go diff --git a/proto/provenance/registry/v1/events.proto b/proto/provenance/registry/v1/events.proto index f888c1f4ba..028ac5a127 100644 --- a/proto/provenance/registry/v1/events.proto +++ b/proto/provenance/registry/v1/events.proto @@ -72,3 +72,6 @@ message EventRegistryClassUpdated { string registry_class_id = 1; string maintainer = 2; } + +// EventParamsUpdated is emitted when the registry module's params are updated via governance. +message EventParamsUpdated {} diff --git a/proto/provenance/registry/v1/genesis.proto b/proto/provenance/registry/v1/genesis.proto index 6ac3617b01..dd282836ab 100644 --- a/proto/provenance/registry/v1/genesis.proto +++ b/proto/provenance/registry/v1/genesis.proto @@ -8,6 +8,7 @@ option java_multiple_files = true; import "gogoproto/gogo.proto"; import "provenance/registry/v1/registry.proto"; import "provenance/registry/v1/authorization.proto"; +import "provenance/registry/v1/params.proto"; // GenesisState defines the registry module's genesis state. // This contains all the registry entries that exist when the blockchain is first initialized. @@ -22,4 +23,7 @@ message GenesisState { // registry_classes is the list of registry classes defining asset class-level authorization rules. repeated RegistryClass registry_classes = 3 [(gogoproto.nullable) = false]; + + // params defines the registry module parameters at genesis. + Params params = 4 [(gogoproto.nullable) = false]; } \ No newline at end of file diff --git a/proto/provenance/registry/v1/params.proto b/proto/provenance/registry/v1/params.proto new file mode 100644 index 0000000000..abefdb42b6 --- /dev/null +++ b/proto/provenance/registry/v1/params.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; +package provenance.registry.v1; + +option go_package = "github.com/provenance-io/provenance/x/registry/types"; +option java_package = "io.provenance.registry.v1"; +option java_multiple_files = true; + +import "gogoproto/gogo.proto"; +import "provenance/registry/v1/authorization.proto"; + +// Params defines the registry module's parameters. +message Params { + // role_authorizations are the default authorization policies that govern role updates for + // registry entries that are not associated with a registry class. They form the middle tier of + // the two-tier resolution: a registry class policy takes precedence, then these module defaults, + // and finally roles without any policy fall back to legacy NFT-owner authorization. + repeated RoleAuthorization role_authorizations = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/provenance/registry/v1/query.proto b/proto/provenance/registry/v1/query.proto index a4c2da8c3a..bd76398f70 100644 --- a/proto/provenance/registry/v1/query.proto +++ b/proto/provenance/registry/v1/query.proto @@ -10,6 +10,7 @@ import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "provenance/registry/v1/registry.proto"; import "provenance/registry/v1/authorization.proto"; +import "provenance/registry/v1/params.proto"; // Query defines the gRPC querier service for the registry module. // This service provides read-only access to registry data and role verification. @@ -51,6 +52,12 @@ service Query { rpc RegistryClasses(QueryRegistryClassesRequest) returns (QueryRegistryClassesResponse) { option (google.api.http).get = "/provenance/registry/v1/registry_classes"; } + + // Params returns the registry module parameters, including the default role authorization + // policies. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = "/provenance/registry/v1/params"; + } } // QueryGetRegistryRequest is the request type for the Query/GetRegistry RPC method. @@ -173,3 +180,12 @@ message QueryRegistryClassesResponse { // pagination is the pagination details for this response. cosmos.base.query.v1beta1.PageResponse pagination = 99; } + +// QueryParamsRequest is the request type for the Query/Params RPC method. +message QueryParamsRequest {} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +message QueryParamsResponse { + // params are the current registry module parameters. + Params params = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index 04663b77da..7a906205f0 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -10,6 +10,7 @@ import "cosmos_proto/cosmos.proto"; import "cosmos/msg/v1/msg.proto"; import "provenance/registry/v1/registry.proto"; import "provenance/registry/v1/authorization.proto"; +import "provenance/registry/v1/params.proto"; // Msg defines the registry Msg service. // This service provides transaction functionality for managing registry entries and roles. @@ -62,6 +63,11 @@ service Msg { // class. Only the current maintainer may update the rules. rpc UpdateRegistryClassRoleAuthorization(MsgUpdateRegistryClassRoleAuthorization) returns (MsgUpdateRegistryClassRoleAuthorizationResponse); + + // UpdateParams is a governance proposal endpoint for updating the registry module's params, + // including the default role authorization policies. The authority must be the governance module + // account address. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); } // MsgRegisterNFT represents a message to register a new NFT in the registry. @@ -292,4 +298,18 @@ message MsgUpdateRegistryClassRoleAuthorization { // MsgUpdateRegistryClassRoleAuthorizationResponse defines the response for // UpdateRegistryClassRoleAuthorization. -message MsgUpdateRegistryClassRoleAuthorizationResponse {} \ No newline at end of file +message MsgUpdateRegistryClassRoleAuthorizationResponse {} + +// MsgUpdateParams is a request to update the registry module's params. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + + // authority should be the governance module account address. + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // params are the new registry module params to set. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response for UpdateParams. +message MsgUpdateParamsResponse {} \ No newline at end of file diff --git a/x/registry/client/cli/query.go b/x/registry/client/cli/query.go index 32d657a79e..8ec09859e5 100644 --- a/x/registry/client/cli/query.go +++ b/x/registry/client/cli/query.go @@ -30,6 +30,7 @@ func CmdQuery() *cobra.Command { GetCmdQueryPendingRoleChanges(), GetCmdQueryRegistryClass(), GetCmdQueryRegistryClasses(), + GetCmdQueryParams(), ) return cmd @@ -285,3 +286,29 @@ func GetCmdQueryRegistryClasses() *cobra.Command { flags.AddPaginationFlagsToCmd(cmd, "registry-classes") return cmd } + +// GetCmdQueryParams returns the command for querying the registry module params. +func GetCmdQueryParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query the registry module parameters", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index 578c801ca6..b75407db3e 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -11,6 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/provenance-io/provenance/x/registry/types" ) @@ -35,6 +37,7 @@ func CmdTx() *cobra.Command { CmdApproveRoleChange(), CmdCreateRegistryClass(), CmdUpdateRegistryClassRoleAuthorization(), + CmdUpdateParams(), ) return cmd @@ -391,7 +394,61 @@ role_authorizations field is used.`, return cmd } -// parseRoleUpdates converts "role=address[,address...]" arguments into RoleUpdate values. +// CmdUpdateParams returns the command to update the registry module params via a governance +// proposal. The params are read from a proto-JSON Params file. This message must be submitted +// through governance; the authority defaults to the governance module account address and may be +// overridden with --authority. +func CmdUpdateParams() *cobra.Command { + cmd := &cobra.Command{ + Use: "update-params ", + Short: "Update the registry module params from a JSON file (governance)", + Long: `Update the registry module params from a proto-JSON Params file, e.g.: +{ + "role_authorizations": [ { "role": "REGISTRY_ROLE_CONTROLLER", "authorizations": [ ... ] } ] +} + +This message must be authorized by governance. The authority defaults to the governance module +account address; use --authority to override. Submit the generated message through +"tx gov submit-proposal".`, + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + jsonData, err := os.ReadFile(args[0]) + if err != nil { + return fmt.Errorf("failed to read file: %w", err) + } + + var params types.Params + if err := clientCtx.Codec.UnmarshalJSON(jsonData, ¶ms); err != nil { + return fmt.Errorf("failed to unmarshal params JSON: %w", err) + } + + authority, err := cmd.Flags().GetString("authority") + if err != nil { + return err + } + if authority == "" { + authority = authtypes.NewModuleAddress(govtypes.ModuleName).String() + } + + msg := types.MsgUpdateParams{ + Authority: authority, + Params: params, + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + cmd.Flags().String("authority", "", "the authority address (defaults to the gov module account)") + flags.AddTxFlagsToCmd(cmd) + return cmd +} + // An empty address list (e.g. "role=") clears the role. func parseRoleUpdates(args []string) ([]types.RoleUpdate, error) { updates := make([]types.RoleUpdate, 0, len(args)) diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go index 7fb1920838..5e6b76bf77 100644 --- a/x/registry/keeper/class.go +++ b/x/registry/keeper/class.go @@ -101,8 +101,9 @@ func (k Keeper) GetRegistryClasses(ctx context.Context, pagination *query.PageRe // // 1. Registry class level (highest priority): if the entry references a registry class that // exists, use the authorization rules defined by that class. -// 2. Static default (fallback): otherwise use the module's static default policies. Roles not -// covered by the returned map fall back to legacy NFT-owner authorization at the call site. +// 2. Module params default (fallback): otherwise use the module's default policies (governance- +// managed via MsgUpdateParams). Roles not covered by the returned map fall back to legacy +// NFT-owner authorization at the call site. func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.RegistryEntry) map[types.RegistryRole]types.RoleAuthorization { if entry != nil && entry.RegistryClassId != "" { class, err := k.GetRegistryClass(ctx, entry.RegistryClassId) @@ -110,5 +111,5 @@ func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.Reg return types.RoleAuthorizationMapFrom(class.RoleAuthorizations) } } - return types.RoleAuthorizationMap() + return k.GetParams(ctx).RoleAuthorizationMap() } diff --git a/x/registry/keeper/genesis.go b/x/registry/keeper/genesis.go index 30a51bec5b..53a60133fd 100644 --- a/x/registry/keeper/genesis.go +++ b/x/registry/keeper/genesis.go @@ -24,6 +24,9 @@ func (k Keeper) InitGenesis(ctx context.Context, state *types.GenesisState) { panic(err) // Genesis should not fail } } + if err := k.Params.Set(ctx, state.Params); err != nil { + panic(err) // Genesis should not fail + } } func (k Keeper) ExportGenesis(ctx context.Context) *types.GenesisState { @@ -53,5 +56,7 @@ func (k Keeper) ExportGenesis(ctx context.Context) *types.GenesisState { panic(err) } + genesis.Params = k.GetParams(ctx) + return genesis } diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index bbb91d3ba5..e2a22be342 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -14,6 +14,9 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" "github.com/cosmos/gogoproto/proto" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/provenance-io/provenance/x/registry/types" ) @@ -24,6 +27,9 @@ type Keeper struct { Registry collections.Map[collections.Pair[string, string], types.RegistryEntry] PendingRoleChanges collections.Map[string, types.PendingRoleChange] RegistryClasses collections.Map[string, types.RegistryClass] + Params collections.Item[types.Params] + + authority string NFTKeeper NFTKeeper MetadataKeeper MetadataKeeper @@ -60,6 +66,15 @@ func NewKeeper(cdc codec.BinaryCodec, storeService store.KVStoreService, nftKeep codec.CollValue[types.RegistryClass](cdc), ), + Params: collections.NewItem( + sb, + collections.NewPrefix(paramsPrefix), + "params", + codec.CollValue[types.Params](cdc), + ), + + authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + NFTKeeper: nftKeeper, MetadataKeeper: metaDataKeeper, } diff --git a/x/registry/keeper/keys.go b/x/registry/keeper/keys.go index 5168f441d1..d2e8c479e8 100644 --- a/x/registry/keeper/keys.go +++ b/x/registry/keeper/keys.go @@ -4,4 +4,5 @@ var ( registryPrefix = []byte{0x01} pendingRoleChangePrefix = []byte{0x02} registryClassPrefix = []byte{0x03} + paramsPrefix = []byte{0x04} ) diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 365f278344..c4f509df0a 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -203,6 +203,21 @@ func (k msgServer) UpdateRegistryClassRoleAuthorization(ctx context.Context, msg return &types.MsgUpdateRegistryClassRoleAuthorizationResponse{}, nil } +// UpdateParams is a governance proposal endpoint for updating the registry module's params, +// including the default role authorization policies. +func (k msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + if err := k.ValidateAuthority(msg.Authority); err != nil { + return nil, err + } + + if err := k.SetParams(ctx, msg.Params); err != nil { + return nil, err + } + + k.EmitEvent(ctx, types.NewEventParamsUpdated()) + return &types.MsgUpdateParamsResponse{}, nil +} + // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry and associated data for the specified key. func (k msgServer) UnregisterNFT(ctx context.Context, msg *types.MsgUnregisterNFT) (*types.MsgUnregisterNFTResponse, error) { diff --git a/x/registry/keeper/params.go b/x/registry/keeper/params.go new file mode 100644 index 0000000000..b92f153033 --- /dev/null +++ b/x/registry/keeper/params.go @@ -0,0 +1,50 @@ +package keeper + +import ( + "context" + "errors" + "fmt" + "strings" + + "cosmossdk.io/collections" + + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/provenance-io/provenance/x/registry/types" +) + +// GetAuthority returns the module's governance authority address. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// IsAuthority returns true if the provided bech32 address is the module's authority. +func (k Keeper) IsAuthority(addr string) bool { + return strings.EqualFold(k.authority, addr) +} + +// ValidateAuthority returns an error if the provided address is not the module's authority. +func (k Keeper) ValidateAuthority(addr string) error { + if !k.IsAuthority(addr) { + return govtypes.ErrInvalidSigner.Wrapf("expected %q got %q", k.GetAuthority(), addr) + } + return nil +} + +// GetParams returns the registry module params. If no params have been set, it returns the module +// defaults. +func (k Keeper) GetParams(ctx context.Context) types.Params { + params, err := k.Params.Get(ctx) + if err != nil { + if errors.Is(err, collections.ErrNotFound) { + return types.DefaultParams() + } + panic(fmt.Errorf("could not get registry params: %w", err)) + } + return params +} + +// SetParams stores the registry module params in state. +func (k Keeper) SetParams(ctx context.Context, params types.Params) error { + return k.Params.Set(ctx, params) +} diff --git a/x/registry/keeper/params_acceptance_test.go b/x/registry/keeper/params_acceptance_test.go new file mode 100644 index 0000000000..50f6785382 --- /dev/null +++ b/x/registry/keeper/params_acceptance_test.go @@ -0,0 +1,191 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/x/nft" + nftkeeper "cosmossdk.io/x/nft/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// ParamsAcceptanceTestSuite covers Phase C2: the registry module's governance-managed params. +// The params hold the default role authorization policies, which form the middle tier of the +// two-tier resolution (registry class policy -> module params default -> NFT-owner fallback). +type ParamsAcceptanceTestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + registryKeeper keeper.Keeper + nftKeeper nftkeeper.Keeper + msgServer types.MsgServer + + authority string + + nftClass nft.Class + + nftOwner string + controller string + stranger string + grantee string +} + +func TestParamsAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(ParamsAcceptanceTestSuite)) +} + +func (s *ParamsAcceptanceTestSuite) SetupTest() { + s.app = app.Setup(s.T()) + s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + + s.nftKeeper = s.app.NFTKeeper + s.registryKeeper = s.app.RegistryKeeper + s.msgServer = keeper.NewMsgServer(s.registryKeeper) + + s.authority = authtypes.NewModuleAddress(govtypes.ModuleName).String() + + s.nftOwner = genAddr() + s.controller = genAddr() + s.stranger = genAddr() + s.grantee = genAddr() + + s.nftClass = nft.Class{Id: "params-test-nft-class"} + s.nftKeeper.SaveClass(s.ctx, s.nftClass) +} + +func (s *ParamsAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { + n := nft.NFT{ClassId: s.nftClass.Id, Id: id} + ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) + s.Require().NoError(err) + s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) + return &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} +} + +// TestDefaultParams confirms the module starts with no role policies, preserving the original +// NFT-owner-only authorization behavior. The CONTROLLER policy is only an example, not a default. +func (s *ParamsAcceptanceTestSuite) TestDefaultParams() { + params := s.registryKeeper.GetParams(s.ctx) + s.Require().NoError(params.Validate()) + s.Require().Empty(params.RoleAuthorizations, "default params should define no role policies") +} + +// TestUpdateParamsAuthority confirms only the governance authority may update params. +func (s *ParamsAcceptanceTestSuite) TestUpdateParamsAuthority() { + newParams := types.Params{ + RoleAuthorizations: []types.RoleAuthorization{servicerRoleAuthorization()}, + } + + s.Run("reject: non-authority signer", func() { + _, err := s.msgServer.UpdateParams(s.ctx, &types.MsgUpdateParams{ + Authority: s.stranger, + Params: newParams, + }) + s.Require().Error(err) + s.Require().ErrorContains(err, "expected") + }) + + s.Run("success: governance authority", func() { + _, err := s.msgServer.UpdateParams(s.ctx, &types.MsgUpdateParams{ + Authority: s.authority, + Params: newParams, + }) + s.Require().NoError(err) + + got := s.registryKeeper.GetParams(s.ctx) + s.Require().Len(got.RoleAuthorizations, 1) + s.Require().Equal(types.RegistryRole_REGISTRY_ROLE_SERVICER, got.RoleAuthorizations[0].Role) + }) +} + +// TestParamsDrivenDefaultTier proves that the middle (params) tier of the two-tier resolution is +// governance-managed: changing params changes the default authorization for entries with no class. +func (s *ParamsAcceptanceTestSuite) TestParamsDrivenDefaultTier() { + roles := []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.controller}}, + } + + // Before changing params: SERVICER has no default policy, so granting it falls back to + // NFT-owner authorization. The controller (not the owner) cannot grant it. + keyBefore := s.mintNFT("nft-before-params") + _, err := s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: keyBefore, + Roles: roles, + }) + s.Require().NoError(err) + + _, err = s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.controller, + Key: keyBefore, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.grantee}, + }) + s.Require().Error(err, "without a SERVICER default policy, controller cannot grant SERVICER") + + // Governance adds a SERVICER default policy (satisfied by the current controller). + _, err = s.msgServer.UpdateParams(s.ctx, &types.MsgUpdateParams{ + Authority: s.authority, + Params: types.Params{ + RoleAuthorizations: []types.RoleAuthorization{servicerRoleAuthorization()}, + }, + }) + s.Require().NoError(err) + + // After changing params: the same operation on a fresh (classless) entry now uses the new + // default policy, so the controller can grant SERVICER. + keyAfter := s.mintNFT("nft-after-params") + _, err = s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: keyAfter, + Roles: roles, + }) + s.Require().NoError(err) + + _, err = s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.controller, + Key: keyAfter, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.grantee}, + }) + s.Require().NoError(err, "with a SERVICER default policy, controller can grant SERVICER") + + hasRole, err := s.registryKeeper.HasRole(s.ctx, keyAfter, types.RegistryRole_REGISTRY_ROLE_SERVICER, s.grantee) + s.Require().NoError(err) + s.Require().True(hasRole) +} + +// TestParamsGenesisRoundTrip verifies params survive an export/import cycle. +func (s *ParamsAcceptanceTestSuite) TestParamsGenesisRoundTrip() { + _, err := s.msgServer.UpdateParams(s.ctx, &types.MsgUpdateParams{ + Authority: s.authority, + Params: types.Params{ + RoleAuthorizations: []types.RoleAuthorization{servicerRoleAuthorization()}, + }, + }) + s.Require().NoError(err) + + exported := s.registryKeeper.ExportGenesis(s.ctx) + s.Require().NoError(exported.Validate()) + s.Require().Len(exported.Params.RoleAuthorizations, 1) + + freshApp := app.Setup(s.T()) + freshCtx := freshApp.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + freshApp.RegistryKeeper.InitGenesis(freshCtx, exported) + + got := freshApp.RegistryKeeper.GetParams(freshCtx) + s.Require().Len(got.RoleAuthorizations, 1) + s.Require().Equal(types.RegistryRole_REGISTRY_ROLE_SERVICER, got.RoleAuthorizations[0].Role) +} diff --git a/x/registry/keeper/query_server.go b/x/registry/keeper/query_server.go index 7a8b5d0dd6..58c7369b7f 100644 --- a/x/registry/keeper/query_server.go +++ b/x/registry/keeper/query_server.go @@ -173,3 +173,14 @@ func (qs QueryServer) RegistryClasses(ctx context.Context, req *types.QueryRegis return &types.QueryRegistryClassesResponse{RegistryClasses: classes, Pagination: pageRes}, nil } + +// Params returns the registry module parameters. +func (qs QueryServer) Params(ctx context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + return &types.QueryParamsResponse{Params: qs.keeper.GetParams(sdkCtx)}, nil +} diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index d332ca4557..b96d836183 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -76,6 +76,12 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) SetupTest() { s.nftClass = nft.Class{Id: "accumulation-test-nft-class-id"} s.nftKeeper.SaveClass(s.ctx, s.nftClass) + + // These tests exercise the CONTROLLER authorization policy, which is not a chain default. Install + // it as the module's default policy so classless entries are governed by it. + s.Require().NoError(s.registryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) } func (s *RoleChangeAccumulationAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { diff --git a/x/registry/types/authorization.go b/x/registry/types/authorization.go index 90e65018d3..c59a902984 100644 --- a/x/registry/types/authorization.go +++ b/x/registry/types/authorization.go @@ -1,19 +1,16 @@ package types -// DefaultRoleAuthorizations returns the static role authorization policies. -// These policies encode who must sign to change each participant role. -// In a future phase, these will be replaced by per-RegistryClass dynamic policies. -func DefaultRoleAuthorizations() []RoleAuthorization { +// ControllerRoleAuthorizations returns the example CONTROLLER authorization policy from the ticket. +// This is NOT the default chain behavior: by default the registry module has no role policies and +// every role change falls back to legacy NFT-owner authorization. This policy set is provided as a +// reusable example that governance can install via MsgUpdateParams, or that a registry-class +// maintainer can adopt for an asset class. +func ControllerRoleAuthorizations() []RoleAuthorization { return []RoleAuthorization{ controllerRoleAuthorization(), } } -// RoleAuthorizationMap returns a map of RegistryRole → RoleAuthorization for fast lookup. -func RoleAuthorizationMap() map[RegistryRole]RoleAuthorization { - return RoleAuthorizationMapFrom(DefaultRoleAuthorizations()) -} - // RoleAuthorizationMapFrom builds a map of RegistryRole → RoleAuthorization from the given slice. // The last entry wins if a role appears more than once. func RoleAuthorizationMapFrom(auths []RoleAuthorization) map[RegistryRole]RoleAuthorization { diff --git a/x/registry/types/events.go b/x/registry/types/events.go index a8509b0893..36a7adb22b 100644 --- a/x/registry/types/events.go +++ b/x/registry/types/events.go @@ -93,6 +93,11 @@ func NewEventRegistryClassUpdated(class *RegistryClass) *EventRegistryClassUpdat } } +// NewEventParamsUpdated returns a new EventParamsUpdated. +func NewEventParamsUpdated() *EventParamsUpdated { + return &EventParamsUpdated{} +} + // GetChangeEvents gets all the events that represent the changes from oldReg to newReg. // Panics if they have different keys (unless oldReg or newReg is nil). func GetChangeEvents(oldReg, newReg *RegistryEntry) ([]*EventRoleGranted, []*EventRoleRevoked) { diff --git a/x/registry/types/events.pb.go b/x/registry/types/events.pb.go index b8ac8e3f0e..d43c0aca91 100644 --- a/x/registry/types/events.pb.go +++ b/x/registry/types/events.pb.go @@ -616,6 +616,43 @@ func (m *EventRegistryClassUpdated) GetMaintainer() string { return "" } +// EventParamsUpdated is emitted when the registry module's params are updated via governance. +type EventParamsUpdated struct { +} + +func (m *EventParamsUpdated) Reset() { *m = EventParamsUpdated{} } +func (m *EventParamsUpdated) String() string { return proto.CompactTextString(m) } +func (*EventParamsUpdated) ProtoMessage() {} +func (*EventParamsUpdated) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{10} +} +func (m *EventParamsUpdated) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventParamsUpdated) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventParamsUpdated.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventParamsUpdated) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventParamsUpdated.Merge(m, src) +} +func (m *EventParamsUpdated) XXX_Size() int { + return m.Size() +} +func (m *EventParamsUpdated) XXX_DiscardUnknown() { + xxx_messageInfo_EventParamsUpdated.DiscardUnknown(m) +} + +var xxx_messageInfo_EventParamsUpdated proto.InternalMessageInfo + func init() { proto.RegisterType((*EventNFTRegistered)(nil), "provenance.registry.v1.EventNFTRegistered") proto.RegisterType((*EventRoleGranted)(nil), "provenance.registry.v1.EventRoleGranted") @@ -627,6 +664,7 @@ func init() { proto.RegisterType((*EventRoleChangeApplied)(nil), "provenance.registry.v1.EventRoleChangeApplied") proto.RegisterType((*EventRegistryClassCreated)(nil), "provenance.registry.v1.EventRegistryClassCreated") proto.RegisterType((*EventRegistryClassUpdated)(nil), "provenance.registry.v1.EventRegistryClassUpdated") + proto.RegisterType((*EventParamsUpdated)(nil), "provenance.registry.v1.EventParamsUpdated") } func init() { @@ -634,35 +672,36 @@ func init() { } var fileDescriptor_61a0995529587ff0 = []byte{ - // 444 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x4d, 0x6f, 0xd4, 0x30, - 0x10, 0x5d, 0xb7, 0x4b, 0xd5, 0x1d, 0x21, 0x3e, 0x2c, 0x28, 0x29, 0xa0, 0xa8, 0x0a, 0x1c, 0x2a, - 0x24, 0x12, 0x55, 0xf0, 0x07, 0xe8, 0x0a, 0xd0, 0x5e, 0x50, 0x09, 0x54, 0x48, 0x5c, 0x2a, 0x77, - 0x3d, 0x4d, 0xad, 0x4d, 0xed, 0x68, 0xec, 0x46, 0xf4, 0xc8, 0x0f, 0x40, 0xf0, 0xb3, 0x38, 0xf6, - 0xc8, 0x11, 0xed, 0xfe, 0x11, 0x14, 0x67, 0xd3, 0xec, 0x76, 0x7b, 0x00, 0x2d, 0x70, 0x8b, 0xdf, - 0x3c, 0xbf, 0x79, 0x7e, 0x76, 0x06, 0x1e, 0x15, 0x64, 0x4a, 0xd4, 0x42, 0x0f, 0x31, 0x21, 0xcc, - 0x94, 0x75, 0x74, 0x96, 0x94, 0x3b, 0x09, 0x96, 0xa8, 0x9d, 0x8d, 0x0b, 0x32, 0xce, 0xf0, 0x8d, - 0x96, 0x14, 0x37, 0xa4, 0xb8, 0xdc, 0x89, 0xde, 0x02, 0x7f, 0x59, 0xf1, 0xde, 0xbc, 0x7a, 0x9f, - 0x7a, 0x18, 0x09, 0x25, 0xbf, 0x0b, 0x6b, 0xfa, 0xc8, 0x1d, 0x28, 0x19, 0xb0, 0x2d, 0xb6, 0xdd, - 0x4b, 0xaf, 0xe9, 0x23, 0x37, 0x90, 0xfc, 0x31, 0xdc, 0x10, 0xd6, 0xa2, 0x3b, 0x18, 0xe6, 0xc2, - 0xda, 0xaa, 0xbc, 0xe2, 0xcb, 0xd7, 0x3d, 0xda, 0xaf, 0xc0, 0x81, 0x8c, 0x3e, 0x33, 0xb8, 0xe5, - 0x35, 0x53, 0x93, 0xe3, 0x6b, 0x12, 0xda, 0x2d, 0xa9, 0xc8, 0x39, 0x74, 0xc9, 0xe4, 0x18, 0xac, - 0xfa, 0x9a, 0xff, 0xe6, 0x0f, 0xa1, 0x27, 0xa4, 0x24, 0xb4, 0x16, 0x6d, 0xd0, 0xdd, 0x5a, 0xdd, - 0xee, 0xa5, 0x2d, 0x30, 0xef, 0x21, 0xc5, 0xd2, 0x8c, 0xfe, 0xbf, 0x87, 0x77, 0x70, 0xa7, 0x89, - 0x76, 0x5f, 0xd3, 0x5f, 0x0a, 0xf7, 0x03, 0x04, 0xf5, 0xb9, 0xa6, 0x77, 0xb8, 0x7b, 0x9a, 0x8f, - 0xf6, 0x0b, 0x29, 0x96, 0xcd, 0x38, 0xfa, 0xca, 0xe0, 0xde, 0x45, 0x62, 0xfd, 0x63, 0xa1, 0x33, - 0xdc, 0x23, 0x53, 0x18, 0xbb, 0x6c, 0x70, 0x0f, 0xa0, 0x37, 0xf4, 0x72, 0x15, 0xa1, 0x4e, 0x6f, - 0xbd, 0x06, 0x06, 0x92, 0xdf, 0x87, 0xf5, 0xa2, 0xee, 0x42, 0x41, 0xb7, 0xae, 0x35, 0xeb, 0x28, - 0x5d, 0x30, 0xf4, 0xa2, 0xf0, 0xaf, 0xf8, 0x92, 0x26, 0x5b, 0xd4, 0x14, 0x35, 0x91, 0xa6, 0x86, - 0x2e, 0xd6, 0x11, 0xc1, 0xc6, 0xa2, 0x66, 0xae, 0xfe, 0xe5, 0x19, 0xa3, 0x2f, 0x0c, 0x36, 0xe7, - 0xee, 0xcc, 0xef, 0xea, 0x13, 0xfa, 0x4b, 0x7b, 0x02, 0xb7, 0x9b, 0xff, 0xb1, 0xed, 0x51, 0x5b, - 0xb8, 0x49, 0xb3, 0x1b, 0x7e, 0xdb, 0x4c, 0x08, 0x70, 0x22, 0x94, 0x76, 0x42, 0x69, 0xa4, 0xa9, - 0x9b, 0x19, 0x24, 0xca, 0xae, 0xb2, 0xd3, 0xbc, 0xa1, 0x3f, 0xb1, 0x33, 0xdf, 0x68, 0xe5, 0x72, - 0xa3, 0xdd, 0xd1, 0xf7, 0x71, 0xc8, 0xce, 0xc7, 0x21, 0xfb, 0x39, 0x0e, 0xd9, 0xb7, 0x49, 0xd8, - 0x39, 0x9f, 0x84, 0x9d, 0x1f, 0x93, 0xb0, 0x03, 0x9b, 0xca, 0xc4, 0x57, 0x0f, 0xa4, 0x3d, 0xf6, - 0xf1, 0x79, 0xa6, 0xdc, 0xf1, 0xe9, 0x61, 0x3c, 0x34, 0x27, 0x49, 0x4b, 0x7a, 0xaa, 0xcc, 0xcc, - 0x2a, 0xf9, 0xd4, 0x8e, 0x3a, 0x77, 0x56, 0xa0, 0x3d, 0x5c, 0xf3, 0x73, 0xee, 0xd9, 0xaf, 0x00, - 0x00, 0x00, 0xff, 0xff, 0xe7, 0x09, 0x9d, 0xa4, 0x0e, 0x05, 0x00, 0x00, + // 453 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x5d, 0x6f, 0xd3, 0x30, + 0x14, 0xad, 0xb7, 0x32, 0xad, 0x57, 0x88, 0x0f, 0x6b, 0x8c, 0x0c, 0x50, 0x34, 0x05, 0x1e, 0x26, + 0x24, 0x12, 0x4d, 0xf0, 0x07, 0x58, 0x05, 0xa8, 0x2f, 0xa8, 0x04, 0x26, 0x24, 0x5e, 0x26, 0xaf, + 0xbe, 0xeb, 0xac, 0xa6, 0x76, 0x74, 0xed, 0x45, 0xec, 0x91, 0x1f, 0x80, 0xe0, 0x67, 0xf1, 0xb8, + 0x47, 0x1e, 0x51, 0xfb, 0x47, 0x50, 0x9d, 0x66, 0x69, 0xc9, 0x1e, 0x40, 0x05, 0xde, 0xe2, 0x73, + 0x8f, 0xcf, 0x3d, 0x3e, 0x76, 0x2e, 0x3c, 0xcc, 0xc9, 0x14, 0xa8, 0x85, 0x1e, 0x60, 0x42, 0x38, + 0x54, 0xd6, 0xd1, 0x79, 0x52, 0xec, 0x27, 0x58, 0xa0, 0x76, 0x36, 0xce, 0xc9, 0x38, 0xc3, 0xb7, + 0x6b, 0x52, 0x5c, 0x91, 0xe2, 0x62, 0x3f, 0x7a, 0x03, 0xfc, 0xc5, 0x8c, 0xf7, 0xfa, 0xe5, 0xbb, + 0xd4, 0xc3, 0x48, 0x28, 0xf9, 0x1d, 0xd8, 0xd0, 0x27, 0xee, 0x48, 0xc9, 0x80, 0xed, 0xb2, 0xbd, + 0x4e, 0x7a, 0x4d, 0x9f, 0xb8, 0x9e, 0xe4, 0x8f, 0xe0, 0x86, 0xb0, 0x16, 0xdd, 0xd1, 0x20, 0x13, + 0xd6, 0xce, 0xca, 0x6b, 0xbe, 0x7c, 0xdd, 0xa3, 0xdd, 0x19, 0xd8, 0x93, 0xd1, 0x27, 0x06, 0xb7, + 0xbc, 0x66, 0x6a, 0x32, 0x7c, 0x45, 0x42, 0xbb, 0x15, 0x15, 0x39, 0x87, 0x36, 0x99, 0x0c, 0x83, + 0x75, 0x5f, 0xf3, 0xdf, 0xfc, 0x01, 0x74, 0x84, 0x94, 0x84, 0xd6, 0xa2, 0x0d, 0xda, 0xbb, 0xeb, + 0x7b, 0x9d, 0xb4, 0x06, 0x96, 0x3d, 0xa4, 0x58, 0x98, 0xd1, 0xff, 0xf7, 0xf0, 0x16, 0xb6, 0xaa, + 0x68, 0x0f, 0x35, 0xfd, 0xa5, 0x70, 0xdf, 0x43, 0x50, 0x9e, 0x6b, 0x7e, 0x87, 0x07, 0x67, 0xd9, + 0xe8, 0x30, 0x97, 0x62, 0xd5, 0x8c, 0xa3, 0x2f, 0x0c, 0xee, 0x5e, 0x26, 0xd6, 0x3d, 0x15, 0x7a, + 0x88, 0x7d, 0x32, 0xb9, 0xb1, 0xab, 0x06, 0x77, 0x1f, 0x3a, 0x03, 0x2f, 0x37, 0x23, 0x94, 0xe9, + 0x6d, 0x96, 0x40, 0x4f, 0xf2, 0x7b, 0xb0, 0x99, 0x97, 0x5d, 0x28, 0x68, 0x97, 0xb5, 0x6a, 0x1d, + 0xa5, 0x0d, 0x43, 0xcf, 0x73, 0xff, 0x8a, 0x7f, 0xd1, 0x64, 0x4d, 0x4d, 0x51, 0x12, 0x69, 0x6e, + 0xe8, 0x72, 0x1d, 0x11, 0x6c, 0x37, 0x35, 0x33, 0xf5, 0x2f, 0xcf, 0x18, 0x7d, 0x66, 0xb0, 0xb3, + 0x74, 0x67, 0x7e, 0x57, 0x97, 0xd0, 0x5f, 0xda, 0x63, 0xb8, 0x5d, 0xfd, 0x8f, 0x75, 0x8f, 0xd2, + 0xc2, 0x4d, 0x5a, 0xdc, 0xf0, 0xdb, 0x66, 0x42, 0x80, 0xb1, 0x50, 0xda, 0x09, 0xa5, 0x91, 0xe6, + 0x6e, 0x16, 0x90, 0x68, 0x78, 0x95, 0x9d, 0xea, 0x0d, 0xfd, 0x89, 0x9d, 0xe5, 0x46, 0x6b, 0x8d, + 0x46, 0x5b, 0xf3, 0xd9, 0xd2, 0x17, 0x24, 0xc6, 0x55, 0x87, 0x83, 0xd1, 0xb7, 0x49, 0xc8, 0x2e, + 0x26, 0x21, 0xfb, 0x31, 0x09, 0xd9, 0xd7, 0x69, 0xd8, 0xba, 0x98, 0x86, 0xad, 0xef, 0xd3, 0xb0, + 0x05, 0x3b, 0xca, 0xc4, 0x57, 0x8f, 0xa9, 0x3e, 0xfb, 0xf0, 0x6c, 0xa8, 0xdc, 0xe9, 0xd9, 0x71, + 0x3c, 0x30, 0xe3, 0xa4, 0x26, 0x3d, 0x51, 0x66, 0x61, 0x95, 0x7c, 0xac, 0x07, 0xa0, 0x3b, 0xcf, + 0xd1, 0x1e, 0x6f, 0xf8, 0xe9, 0xf7, 0xf4, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0x1c, 0x1c, + 0x15, 0x24, 0x05, 0x00, 0x00, } func (m *EventNFTRegistered) Marshal() (dAtA []byte, err error) { @@ -1095,6 +1134,29 @@ func (m *EventRegistryClassUpdated) MarshalToSizedBuffer(dAtA []byte) (int, erro return len(dAtA) - i, nil } +func (m *EventParamsUpdated) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventParamsUpdated) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventParamsUpdated) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -1312,6 +1374,15 @@ func (m *EventRegistryClassUpdated) Size() (n int) { return n } +func (m *EventParamsUpdated) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovEvents(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -2714,6 +2785,56 @@ func (m *EventRegistryClassUpdated) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventParamsUpdated) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventParamsUpdated: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventParamsUpdated: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/genesis.go b/x/registry/types/genesis.go index 2906668138..f49b01e2a2 100644 --- a/x/registry/types/genesis.go +++ b/x/registry/types/genesis.go @@ -4,7 +4,9 @@ import "fmt" // DefaultGenesis returns the default genesis state. func DefaultGenesis() *GenesisState { - return &GenesisState{} + return &GenesisState{ + Params: DefaultParams(), + } } // Validate validates the GenesisState. @@ -26,5 +28,9 @@ func (m *GenesisState) Validate() error { seenClasses[class.RegistryClassId] = true } + if err := m.Params.Validate(); err != nil { + return fmt.Errorf("params: %w", err) + } + return nil } diff --git a/x/registry/types/genesis.pb.go b/x/registry/types/genesis.pb.go index 81891f5008..a551d54b6e 100644 --- a/x/registry/types/genesis.pb.go +++ b/x/registry/types/genesis.pb.go @@ -34,6 +34,8 @@ type GenesisState struct { PendingRoleChanges []PendingRoleChange `protobuf:"bytes,2,rep,name=pending_role_changes,json=pendingRoleChanges,proto3" json:"pending_role_changes"` // registry_classes is the list of registry classes defining asset class-level authorization rules. RegistryClasses []RegistryClass `protobuf:"bytes,3,rep,name=registry_classes,json=registryClasses,proto3" json:"registry_classes"` + // params defines the registry module parameters at genesis. + Params Params `protobuf:"bytes,4,opt,name=params,proto3" json:"params"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -90,6 +92,13 @@ func (m *GenesisState) GetRegistryClasses() []RegistryClass { return nil } +func (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + func init() { proto.RegisterType((*GenesisState)(nil), "provenance.registry.v1.GenesisState") } @@ -99,27 +108,29 @@ func init() { } var fileDescriptor_1652bcf54f1aa9cb = []byte{ - // 309 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0xcd, 0x4a, 0x33, 0x31, - 0x14, 0x86, 0x67, 0xda, 0x8f, 0x4f, 0x88, 0x82, 0x32, 0x14, 0xa9, 0x5d, 0x44, 0x11, 0x0b, 0x2a, - 0x98, 0x50, 0xf5, 0x0a, 0x5a, 0x8a, 0xdb, 0x52, 0xc1, 0x85, 0x9b, 0x92, 0x8e, 0x87, 0x34, 0x58, - 0x73, 0x86, 0x24, 0x2d, 0xd6, 0xab, 0xe8, 0x65, 0x75, 0xd9, 0xa5, 0x2b, 0x91, 0x99, 0x1b, 0x91, - 0xce, 0x8f, 0x53, 0xc4, 0x01, 0x77, 0x27, 0x39, 0xcf, 0xfb, 0xf0, 0xc2, 0x21, 0x67, 0x91, 0xc1, - 0x39, 0x68, 0xa1, 0x43, 0xe0, 0x06, 0xa4, 0xb2, 0xce, 0x2c, 0xf8, 0xbc, 0xc3, 0x25, 0x68, 0xb0, - 0xca, 0xb2, 0xc8, 0xa0, 0xc3, 0xe0, 0xb0, 0xa4, 0x58, 0x41, 0xb1, 0x79, 0xa7, 0xd5, 0x90, 0x28, - 0x31, 0x45, 0xf8, 0x66, 0xca, 0xe8, 0x56, 0xbb, 0xc2, 0xf9, 0x9d, 0xcc, 0xb0, 0xcb, 0x0a, 0x4c, - 0xcc, 0xdc, 0x04, 0x8d, 0x7a, 0x13, 0x4e, 0xa1, 0xce, 0xd8, 0xd3, 0x65, 0x8d, 0xec, 0xdd, 0x65, - 0x95, 0xee, 0x9d, 0x70, 0x10, 0xf4, 0xc9, 0x0e, 0x68, 0x67, 0x14, 0xd8, 0xa6, 0x7f, 0x52, 0x3f, - 0xdf, 0xbd, 0x6e, 0xb3, 0xdf, 0x3b, 0xb2, 0x61, 0x3e, 0xf7, 0xb5, 0x33, 0x8b, 0xee, 0xbf, 0xd5, - 0xc7, 0xb1, 0x37, 0x2c, 0xb2, 0x81, 0x20, 0x8d, 0x08, 0xf4, 0x93, 0xd2, 0x72, 0x64, 0x70, 0x0a, - 0xa3, 0x70, 0x22, 0xb4, 0x04, 0xdb, 0xac, 0xa5, 0xce, 0x8b, 0x2a, 0xe7, 0x20, 0xcb, 0x0c, 0x71, - 0x0a, 0xbd, 0x34, 0x91, 0x7b, 0x83, 0xe8, 0xe7, 0xc2, 0x06, 0x0f, 0xe4, 0xa0, 0x88, 0x8e, 0xc2, - 0xa9, 0xb0, 0x16, 0x6c, 0xb3, 0xfe, 0xb7, 0xca, 0xbd, 0x0d, 0x9e, 0xab, 0xf7, 0xcd, 0xf6, 0x27, - 0xd8, 0xee, 0xf3, 0x2a, 0xa6, 0xfe, 0x3a, 0xa6, 0xfe, 0x67, 0x4c, 0xfd, 0x65, 0x42, 0xbd, 0x75, - 0x42, 0xbd, 0xf7, 0x84, 0x7a, 0xe4, 0x48, 0x61, 0x85, 0x79, 0xe0, 0x3f, 0xde, 0x4a, 0xe5, 0x26, - 0xb3, 0x31, 0x0b, 0xf1, 0x85, 0x97, 0xd0, 0x95, 0xc2, 0xad, 0x17, 0x7f, 0x2d, 0x0f, 0xe2, 0x16, - 0x11, 0xd8, 0xf1, 0xff, 0xf4, 0x0c, 0x37, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x78, 0xdf, - 0x24, 0x2f, 0x02, 0x00, 0x00, + // 337 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x91, 0x41, 0x4b, 0x02, 0x41, + 0x14, 0xc7, 0x77, 0x55, 0x0c, 0xc6, 0xa0, 0x58, 0x24, 0x36, 0x0f, 0x93, 0x54, 0x82, 0x05, 0xed, + 0xa2, 0x75, 0xec, 0xa4, 0x48, 0x57, 0x31, 0xe8, 0xd0, 0x45, 0xc6, 0xed, 0x31, 0x0e, 0xe9, 0xbc, + 0x65, 0x66, 0x94, 0xec, 0x53, 0xf4, 0xb1, 0x3c, 0x85, 0xc7, 0x4e, 0x11, 0xfa, 0x45, 0xc2, 0xdd, + 0x31, 0x25, 0x5a, 0xe8, 0xf6, 0x76, 0xe7, 0xf7, 0xff, 0xf1, 0x9f, 0x79, 0xe4, 0x3c, 0x56, 0x38, + 0x05, 0xc9, 0x64, 0x04, 0xa1, 0x02, 0x2e, 0xb4, 0x51, 0xb3, 0x70, 0xda, 0x08, 0x39, 0x48, 0xd0, + 0x42, 0x07, 0xb1, 0x42, 0x83, 0xde, 0xd1, 0x96, 0x0a, 0x36, 0x54, 0x30, 0x6d, 0x54, 0xca, 0x1c, + 0x39, 0x26, 0x48, 0xb8, 0x9e, 0x52, 0xba, 0x52, 0xcb, 0x70, 0xfe, 0x24, 0x53, 0xec, 0x32, 0x03, + 0x63, 0x13, 0x33, 0x44, 0x25, 0x5e, 0x99, 0x11, 0x28, 0x2d, 0x7b, 0x96, 0xc1, 0xc6, 0x4c, 0xb1, + 0xb1, 0x6d, 0x79, 0xfa, 0x9e, 0x23, 0xfb, 0x77, 0x69, 0xef, 0x7b, 0xc3, 0x0c, 0x78, 0x1d, 0xb2, + 0x07, 0xd2, 0x28, 0x01, 0xda, 0x77, 0xab, 0xf9, 0x7a, 0xa9, 0x59, 0x0b, 0xfe, 0xbe, 0x48, 0xd0, + 0xb3, 0x73, 0x47, 0x1a, 0x35, 0x6b, 0x15, 0xe6, 0x9f, 0x27, 0x4e, 0x6f, 0x93, 0xf5, 0x18, 0x29, + 0xc7, 0x20, 0x9f, 0x84, 0xe4, 0x7d, 0x85, 0x23, 0xe8, 0x47, 0x43, 0x26, 0x39, 0x68, 0x3f, 0x97, + 0x38, 0x2f, 0xb2, 0x9c, 0xdd, 0x34, 0xd3, 0xc3, 0x11, 0xb4, 0x93, 0x84, 0xf5, 0x7a, 0xf1, 0xef, + 0x03, 0xed, 0x3d, 0x90, 0xc3, 0x4d, 0xb4, 0x1f, 0x8d, 0x98, 0xd6, 0xa0, 0xfd, 0xfc, 0xff, 0x2a, + 0xb7, 0xd7, 0xb8, 0x55, 0x1f, 0xa8, 0xdd, 0x9f, 0xa0, 0xbd, 0x5b, 0x52, 0x4c, 0x9f, 0xc8, 0x2f, + 0x54, 0xdd, 0x7a, 0xa9, 0x49, 0x33, 0xcb, 0x26, 0x94, 0xd5, 0xd8, 0x4c, 0xeb, 0x79, 0xbe, 0xa4, + 0xee, 0x62, 0x49, 0xdd, 0xaf, 0x25, 0x75, 0xdf, 0x56, 0xd4, 0x59, 0xac, 0xa8, 0xf3, 0xb1, 0xa2, + 0x0e, 0x39, 0x16, 0x98, 0x61, 0xea, 0xba, 0x8f, 0x37, 0x5c, 0x98, 0xe1, 0x64, 0x10, 0x44, 0x38, + 0x0e, 0xb7, 0xd0, 0x95, 0xc0, 0x9d, 0xaf, 0xf0, 0x65, 0xbb, 0x47, 0x33, 0x8b, 0x41, 0x0f, 0x8a, + 0xc9, 0x12, 0xaf, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x9c, 0xbe, 0x51, 0x50, 0x92, 0x02, 0x00, + 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -142,6 +153,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 if len(m.RegistryClasses) > 0 { for iNdEx := len(m.RegistryClasses) - 1; iNdEx >= 0; iNdEx-- { { @@ -222,6 +243,8 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) return n } @@ -362,6 +385,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 88be15db86..7f88d85466 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -19,6 +19,7 @@ var AllRequestMsgs = []sdk.Msg{ (*MsgApproveRoleChange)(nil), (*MsgCreateRegistryClass)(nil), (*MsgUpdateRegistryClassRoleAuthorization)(nil), + (*MsgUpdateParams)(nil), } // ValidateBasic validates the MsgRegisterNFT message @@ -221,6 +222,20 @@ func (m MsgUpdateRegistryClassRoleAuthorization) ValidateBasic() error { return errors.Join(errs...) } +// ValidateBasic validates the MsgUpdateParams message +func (m MsgUpdateParams) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Authority); err != nil { + errs = append(errs, NewErrCodeInvalidField("authority", "%s", err)) + } + + if err := m.Params.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("params", "%s", err)) + } + + return errors.Join(errs...) +} + // ValidateBasic validates the MsgUnregisterNFT message func (m MsgUnregisterNFT) ValidateBasic() error { var errs []error diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 64759a1e01..8f7dcf046d 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -22,6 +22,7 @@ func TestAllMsgsGetSigners(t *testing.T) { func(signer string) sdk.Msg { return &MsgSetRoles{Signer: signer} }, func(signer string) sdk.Msg { return &MsgCreateRegistryClass{Signer: signer} }, func(signer string) sdk.Msg { return &MsgUpdateRegistryClassRoleAuthorization{Signer: signer} }, + func(signer string) sdk.Msg { return &MsgUpdateParams{Authority: signer} }, } msgMakersMulti := []testutil.MsgMakerMulti{} diff --git a/x/registry/types/params.go b/x/registry/types/params.go new file mode 100644 index 0000000000..4866a88c90 --- /dev/null +++ b/x/registry/types/params.go @@ -0,0 +1,27 @@ +package types + +import ( + "errors" +) + +// DefaultParams returns the default registry module parameters. By default the module defines no +// role authorization policies, which preserves the original chain behavior: every role change +// falls back to legacy NFT-owner authorization. Governance can install policies via +// MsgUpdateParams, and registry-class maintainers can define per-asset-class policies. +func DefaultParams() Params { + return Params{} +} + +// Validate validates the Params. +func (p Params) Validate() error { + if errs := validateRoleAuthorizations(p.RoleAuthorizations); len(errs) > 0 { + return errors.Join(errs...) + } + return nil +} + +// RoleAuthorizationMap returns a map of RegistryRole -> RoleAuthorization for the params' default +// policies, for fast lookup during authorization resolution. +func (p Params) RoleAuthorizationMap() map[RegistryRole]RoleAuthorization { + return RoleAuthorizationMapFrom(p.RoleAuthorizations) +} diff --git a/x/registry/types/params.pb.go b/x/registry/types/params.pb.go new file mode 100644 index 0000000000..2caa423309 --- /dev/null +++ b/x/registry/types/params.pb.go @@ -0,0 +1,338 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: provenance/registry/v1/params.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// Params defines the registry module's parameters. +type Params struct { + // role_authorizations are the default authorization policies that govern role updates for + // registry entries that are not associated with a registry class. They form the middle tier of + // the two-tier resolution: a registry class policy takes precedence, then these module defaults, + // and finally roles without any policy fall back to legacy NFT-owner authorization. + RoleAuthorizations []RoleAuthorization `protobuf:"bytes,1,rep,name=role_authorizations,json=roleAuthorizations,proto3" json:"role_authorizations"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_5e5f5edf5698b7c6, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetRoleAuthorizations() []RoleAuthorization { + if m != nil { + return m.RoleAuthorizations + } + return nil +} + +func init() { + proto.RegisterType((*Params)(nil), "provenance.registry.v1.Params") +} + +func init() { + proto.RegisterFile("provenance/registry/v1/params.proto", fileDescriptor_5e5f5edf5698b7c6) +} + +var fileDescriptor_5e5f5edf5698b7c6 = []byte{ + // 225 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2e, 0x28, 0xca, 0x2f, + 0x4b, 0xcd, 0x4b, 0xcc, 0x4b, 0x4e, 0xd5, 0x2f, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, 0x29, 0xaa, 0xd4, + 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, + 0x12, 0x43, 0x28, 0xd2, 0x83, 0x29, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, + 0x2b, 0xd1, 0x07, 0xb1, 0x20, 0xaa, 0xa5, 0xb4, 0x70, 0x18, 0x99, 0x58, 0x5a, 0x92, 0x91, 0x5f, + 0x94, 0x59, 0x95, 0x58, 0x92, 0x99, 0x9f, 0x07, 0x51, 0xab, 0x94, 0xc5, 0xc5, 0x16, 0x00, 0xb6, + 0x49, 0x28, 0x81, 0x4b, 0xb8, 0x28, 0x3f, 0x27, 0x35, 0x1e, 0x45, 0x55, 0xb1, 0x04, 0xa3, 0x02, + 0xb3, 0x06, 0xb7, 0x91, 0xa6, 0x1e, 0x76, 0x17, 0xe8, 0x05, 0xe5, 0xe7, 0xa4, 0x3a, 0x22, 0xeb, + 0x70, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x48, 0xa8, 0x08, 0x5d, 0xa2, 0xd8, 0x29, 0xfb, 0xc4, + 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, + 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xb8, 0x24, 0x33, 0xf3, 0x71, 0x58, 0x10, 0xc0, + 0x18, 0x65, 0x92, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0x50, 0xa4, + 0x9b, 0x99, 0x8f, 0xc4, 0xd3, 0xaf, 0x40, 0xf8, 0xb4, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, + 0xec, 0x3f, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x69, 0x29, 0xb2, 0xb7, 0x60, 0x01, 0x00, + 0x00, +} + +func (m *Params) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RoleAuthorizations) > 0 { + for iNdEx := len(m.RoleAuthorizations) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleAuthorizations[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.RoleAuthorizations) > 0 { + for _, e := range m.RoleAuthorizations { + l = e.Size() + n += 1 + l + sovParams(uint64(l)) + } + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleAuthorizations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleAuthorizations = append(m.RoleAuthorizations, RoleAuthorization{}) + if err := m.RoleAuthorizations[len(m.RoleAuthorizations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowParams + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/registry/types/query.pb.go b/x/registry/types/query.pb.go index eea08e3232..09edc6478a 100644 --- a/x/registry/types/query.pb.go +++ b/x/registry/types/query.pb.go @@ -753,6 +753,89 @@ func (m *QueryRegistryClassesResponse) GetPagination() *query.PageResponse { return nil } +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +func (m *QueryParamsRequest) Reset() { *m = QueryParamsRequest{} } +func (m *QueryParamsRequest) String() string { return proto.CompactTextString(m) } +func (*QueryParamsRequest) ProtoMessage() {} +func (*QueryParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{14} +} +func (m *QueryParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsRequest.Merge(m, src) +} +func (m *QueryParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsRequest proto.InternalMessageInfo + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params are the current registry module parameters. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *QueryParamsResponse) Reset() { *m = QueryParamsResponse{} } +func (m *QueryParamsResponse) String() string { return proto.CompactTextString(m) } +func (*QueryParamsResponse) ProtoMessage() {} +func (*QueryParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{15} +} +func (m *QueryParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryParamsResponse.Merge(m, src) +} +func (m *QueryParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo + +func (m *QueryParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + func init() { proto.RegisterType((*QueryGetRegistryRequest)(nil), "provenance.registry.v1.QueryGetRegistryRequest") proto.RegisterType((*QueryGetRegistryResponse)(nil), "provenance.registry.v1.QueryGetRegistryResponse") @@ -768,6 +851,8 @@ func init() { proto.RegisterType((*QueryRegistryClassResponse)(nil), "provenance.registry.v1.QueryRegistryClassResponse") proto.RegisterType((*QueryRegistryClassesRequest)(nil), "provenance.registry.v1.QueryRegistryClassesRequest") proto.RegisterType((*QueryRegistryClassesResponse)(nil), "provenance.registry.v1.QueryRegistryClassesResponse") + proto.RegisterType((*QueryParamsRequest)(nil), "provenance.registry.v1.QueryParamsRequest") + proto.RegisterType((*QueryParamsResponse)(nil), "provenance.registry.v1.QueryParamsResponse") } func init() { @@ -775,65 +860,70 @@ func init() { } var fileDescriptor_c166c561e401a2eb = []byte{ - // 922 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x41, 0x8f, 0xdb, 0x44, - 0x14, 0xde, 0xd9, 0x2d, 0xec, 0xf2, 0xca, 0x6e, 0xd9, 0xe9, 0x0a, 0xb2, 0xa1, 0xa4, 0x91, 0x69, - 0x21, 0x84, 0xe2, 0xd9, 0xa4, 0x5b, 0xa8, 0x10, 0xa7, 0x14, 0x08, 0xa5, 0x97, 0x60, 0x04, 0x07, - 0x38, 0x44, 0x93, 0x64, 0x70, 0xac, 0x4d, 0x3d, 0xae, 0xc7, 0x89, 0x08, 0x51, 0x0e, 0x70, 0xe3, - 0x86, 0xc4, 0x85, 0x1b, 0xfd, 0x05, 0x1c, 0x38, 0x02, 0x12, 0xe2, 0xd6, 0x0b, 0x52, 0x25, 0x2e, - 0x9c, 0x10, 0xda, 0xe5, 0x87, 0x20, 0x8f, 0xc7, 0x89, 0x9d, 0xd8, 0x75, 0xac, 0xcd, 0x2d, 0x9e, - 0x99, 0xef, 0xbd, 0xef, 0xfb, 0xde, 0xdb, 0xf7, 0xb4, 0xa0, 0x39, 0x2e, 0x1f, 0x31, 0x9b, 0xda, - 0x5d, 0x46, 0x5c, 0x66, 0x5a, 0xc2, 0x73, 0xc7, 0x64, 0x54, 0x23, 0x0f, 0x86, 0xcc, 0x1d, 0xeb, - 0x8e, 0xcb, 0x3d, 0x8e, 0x9f, 0x9f, 0xbf, 0xd1, 0xc3, 0x37, 0xfa, 0xa8, 0x56, 0xac, 0x76, 0xb9, - 0xb8, 0xcf, 0x05, 0xe9, 0x50, 0xc1, 0x02, 0x00, 0x19, 0xd5, 0x3a, 0xcc, 0xa3, 0x35, 0xe2, 0x50, - 0xd3, 0xb2, 0xa9, 0x67, 0x71, 0x3b, 0x88, 0x51, 0x3c, 0x30, 0xb9, 0xc9, 0xe5, 0x4f, 0xe2, 0xff, - 0x52, 0xa7, 0x57, 0x4c, 0xce, 0xcd, 0x01, 0x23, 0xd4, 0xb1, 0x08, 0xb5, 0x6d, 0xee, 0x49, 0x88, - 0x50, 0xb7, 0xd7, 0x53, 0xb8, 0xcd, 0x38, 0x04, 0xcf, 0xaa, 0x29, 0xcf, 0xe8, 0xd0, 0xeb, 0x73, - 0xd7, 0xfa, 0x2a, 0x42, 0x43, 0x6b, 0xc1, 0x0b, 0x1f, 0xf9, 0x44, 0x9b, 0xcc, 0x33, 0xd4, 0x53, - 0x83, 0x3d, 0x18, 0x32, 0xe1, 0xe1, 0x5b, 0xb0, 0x75, 0xc2, 0xc6, 0x05, 0x54, 0x46, 0x95, 0x8b, - 0xf5, 0x97, 0xf5, 0x64, 0xcd, 0x7a, 0x88, 0xba, 0xc7, 0xc6, 0x86, 0xff, 0x5e, 0xeb, 0x42, 0x61, - 0x39, 0xa2, 0x70, 0xb8, 0x2d, 0x18, 0x6e, 0xc2, 0x4e, 0x88, 0x55, 0x71, 0xaf, 0x67, 0xc5, 0x7d, - 0xcf, 0xf6, 0xdc, 0x71, 0xe3, 0xc2, 0xa3, 0x7f, 0xae, 0x6e, 0x18, 0x33, 0xb0, 0xf6, 0x2d, 0x82, - 0xc3, 0x85, 0x2c, 0x16, 0x13, 0x21, 0xf3, 0x6b, 0xb0, 0x47, 0x85, 0x60, 0x5e, 0xbb, 0x3b, 0xa0, - 0x42, 0xb4, 0xad, 0x9e, 0x4c, 0xf6, 0x8c, 0xf1, 0xac, 0x3c, 0xbd, 0xe3, 0x1f, 0xde, 0xed, 0xe1, - 0xf7, 0x01, 0xe6, 0x55, 0x29, 0x74, 0x25, 0x9d, 0x57, 0xf4, 0xa0, 0x84, 0xba, 0x5f, 0x42, 0x3d, - 0xa8, 0xb9, 0x2a, 0xa1, 0xde, 0xa2, 0x26, 0x53, 0x19, 0x8c, 0x08, 0x52, 0xfb, 0x19, 0x41, 0x31, - 0x89, 0x8b, 0xd2, 0x7c, 0x0f, 0xc0, 0x9d, 0x9d, 0x16, 0x50, 0x79, 0x2b, 0xaf, 0xea, 0x08, 0x1c, - 0x37, 0x13, 0x38, 0xbf, 0x9a, 0xc9, 0x39, 0x60, 0x12, 0x23, 0xfd, 0x10, 0xc1, 0x65, 0x49, 0xfa, - 0x03, 0x2a, 0x0c, 0x3e, 0x60, 0xe7, 0x2b, 0x3a, 0x2e, 0xc0, 0x36, 0xed, 0xf5, 0x5c, 0x26, 0x44, - 0x61, 0x53, 0x5a, 0x1d, 0x7e, 0xe2, 0xdb, 0x70, 0xc1, 0xe5, 0x03, 0x56, 0xd8, 0x2a, 0xa3, 0xca, - 0x5e, 0xfd, 0x5a, 0x56, 0x44, 0xc9, 0x45, 0x22, 0xb4, 0x1a, 0x1c, 0xc4, 0x19, 0x2a, 0x43, 0x0f, - 0x61, 0xa7, 0x4f, 0x45, 0x5b, 0x46, 0xf5, 0x79, 0xee, 0x18, 0xdb, 0xfd, 0xe0, 0x89, 0x46, 0xe0, - 0x25, 0x09, 0x69, 0x31, 0xbb, 0x67, 0xd9, 0xa6, 0x7f, 0x76, 0xa7, 0x4f, 0xed, 0x59, 0xdd, 0xf0, - 0x1e, 0x6c, 0xce, 0xba, 0x61, 0xd3, 0xea, 0x69, 0x5f, 0x23, 0x28, 0xa5, 0x21, 0x54, 0xba, 0x36, - 0x5c, 0x76, 0x82, 0x4b, 0x99, 0xb2, 0xdd, 0x95, 0xd7, 0xca, 0xa1, 0xd7, 0xd2, 0xf4, 0x2c, 0xc5, - 0x53, 0xc5, 0xdc, 0x77, 0x16, 0x2f, 0xb4, 0x1f, 0x53, 0x39, 0x88, 0x73, 0x56, 0x65, 0x5d, 0x1d, - 0xfe, 0x27, 0x82, 0xab, 0xa9, 0x0c, 0x95, 0x4d, 0x14, 0x0e, 0x12, 0x6c, 0x0a, 0x1b, 0x3e, 0xb7, - 0x4f, 0x78, 0xc9, 0xa7, 0x35, 0x36, 0x7f, 0x53, 0x0d, 0x8f, 0xd0, 0x30, 0x39, 0x11, 0x42, 0xaf, - 0xab, 0xb0, 0x1f, 0x12, 0x5c, 0x9c, 0x1f, 0x97, 0xdc, 0x28, 0xe0, 0x6e, 0x4f, 0x73, 0xd4, 0x5f, - 0xfe, 0x42, 0x20, 0x65, 0x89, 0x01, 0x7b, 0xf1, 0x48, 0xab, 0xce, 0x3c, 0x19, 0x46, 0x19, 0xb1, - 0x1b, 0xcb, 0xa9, 0x31, 0x78, 0x71, 0x39, 0xe3, 0xbc, 0x51, 0xd6, 0x55, 0xf1, 0xdf, 0x11, 0x5c, - 0x49, 0xce, 0xa3, 0xb4, 0x7d, 0x0a, 0xcf, 0xc5, 0xb5, 0xad, 0x3e, 0xdb, 0xa2, 0xea, 0xe2, 0x8e, - 0xae, 0xb1, 0xc6, 0xf5, 0x1f, 0x00, 0x9e, 0x92, 0x0a, 0xf0, 0x6f, 0x08, 0x2e, 0x46, 0x96, 0x11, - 0x26, 0x69, 0x04, 0x53, 0x16, 0x61, 0xf1, 0x68, 0x75, 0x40, 0x40, 0x44, 0xfb, 0xf0, 0x9b, 0xbf, - 0xfe, 0xfb, 0x7e, 0xf3, 0x5d, 0xdc, 0x20, 0x19, 0x1b, 0x9b, 0x4c, 0x4e, 0xd8, 0x58, 0x8f, 0x2f, - 0xab, 0x69, 0x70, 0x68, 0x7f, 0xe1, 0xf9, 0x1f, 0xf8, 0x21, 0x82, 0xdd, 0xd8, 0x66, 0xc1, 0xb5, - 0x15, 0xf9, 0xcc, 0x37, 0x62, 0xb1, 0x9e, 0x07, 0xa2, 0x44, 0x54, 0xa4, 0x08, 0x0d, 0x97, 0xb3, - 0x44, 0xe0, 0x3f, 0x10, 0x6c, 0xab, 0x29, 0x8d, 0x5f, 0x7f, 0x62, 0xa6, 0xf8, 0xb6, 0x29, 0xde, - 0x58, 0xed, 0xb1, 0x22, 0xf4, 0xb9, 0x24, 0xf4, 0x09, 0xfe, 0x38, 0x8d, 0x50, 0xb8, 0x16, 0xb2, - 0x5d, 0x25, 0x13, 0xb5, 0x9f, 0xa6, 0x64, 0xe2, 0x23, 0xa6, 0x7e, 0x97, 0xec, 0x2f, 0x0d, 0x23, - 0x7c, 0xeb, 0x89, 0x04, 0xd3, 0xd6, 0x4c, 0xf1, 0xcd, 0xbc, 0x30, 0xa5, 0xf0, 0xb6, 0x54, 0x58, - 0xc7, 0x47, 0x69, 0x0a, 0x13, 0x46, 0x2c, 0x99, 0xf8, 0x5d, 0xf2, 0x2b, 0x02, 0xbc, 0x3c, 0x9d, - 0x71, 0x4e, 0x22, 0xb3, 0x7e, 0x79, 0x2b, 0x37, 0x4e, 0x29, 0x38, 0x96, 0x0a, 0x74, 0x7c, 0x23, - 0x87, 0x02, 0x81, 0x7f, 0x41, 0xb0, 0x1b, 0x1b, 0x0f, 0x19, 0x3d, 0x9e, 0x34, 0xb8, 0x33, 0x7a, - 0x3c, 0x71, 0x44, 0x6b, 0x0d, 0x49, 0xf7, 0x1d, 0xfc, 0x76, 0x56, 0x8f, 0x07, 0x7d, 0x44, 0x26, - 0x4b, 0xab, 0x61, 0x8a, 0x7f, 0x42, 0x70, 0x69, 0x61, 0x4c, 0xe2, 0x9b, 0xab, 0x73, 0x99, 0x9b, - 0x7e, 0x9c, 0x0f, 0xa4, 0x24, 0x1c, 0x49, 0x09, 0x55, 0x5c, 0x59, 0x4d, 0x02, 0x13, 0x8d, 0x93, - 0x47, 0xa7, 0x25, 0xf4, 0xf8, 0xb4, 0x84, 0xfe, 0x3d, 0x2d, 0xa1, 0xef, 0xce, 0x4a, 0x1b, 0x8f, - 0xcf, 0x4a, 0x1b, 0x7f, 0x9f, 0x95, 0x36, 0xe0, 0xd0, 0xe2, 0x29, 0x1c, 0x5a, 0xe8, 0xb3, 0x63, - 0xd3, 0xf2, 0xfa, 0xc3, 0x8e, 0xde, 0xe5, 0xf7, 0x23, 0xa9, 0xde, 0xb0, 0x78, 0x34, 0xf1, 0x97, - 0xf3, 0xd4, 0xde, 0xd8, 0x61, 0xa2, 0xf3, 0xb4, 0xfc, 0x3f, 0xe3, 0xe6, 0xff, 0x01, 0x00, 0x00, - 0xff, 0xff, 0x6b, 0xed, 0xbf, 0x4c, 0x58, 0x0d, 0x00, 0x00, + // 993 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0x1b, 0x45, + 0x14, 0xce, 0x24, 0x25, 0x09, 0x2f, 0x24, 0x25, 0xd3, 0x08, 0x1c, 0x53, 0xb6, 0xd6, 0xf6, 0x07, + 0xc1, 0x2d, 0x3b, 0xb1, 0x9b, 0x42, 0x85, 0x7a, 0x4a, 0x81, 0x50, 0x7a, 0x09, 0x5b, 0xc1, 0x01, + 0x0e, 0xd6, 0xc4, 0x1e, 0xd6, 0xab, 0x24, 0x3b, 0xdb, 0x9d, 0x4d, 0x84, 0xb1, 0x72, 0x80, 0x5b, + 0x6f, 0x48, 0xfc, 0x01, 0xf4, 0xc0, 0x99, 0x03, 0x47, 0x40, 0x42, 0xdc, 0x7a, 0x41, 0xaa, 0xc4, + 0x85, 0x13, 0x42, 0x09, 0x7f, 0x48, 0xb5, 0xb3, 0x6f, 0xed, 0x5d, 0xdb, 0x9b, 0xb5, 0x95, 0xdc, + 0x76, 0x67, 0xde, 0xf7, 0xde, 0xf7, 0xbd, 0xf7, 0xfc, 0x9e, 0x17, 0x4c, 0x3f, 0x90, 0x87, 0xc2, + 0xe3, 0x5e, 0x53, 0xb0, 0x40, 0x38, 0xae, 0x0a, 0x83, 0x0e, 0x3b, 0xac, 0xb1, 0xc7, 0x07, 0x22, + 0xe8, 0x58, 0x7e, 0x20, 0x43, 0x49, 0x5f, 0xeb, 0xdb, 0x58, 0x89, 0x8d, 0x75, 0x58, 0x2b, 0x57, + 0x9b, 0x52, 0xed, 0x4b, 0xc5, 0x76, 0xb8, 0x12, 0x31, 0x80, 0x1d, 0xd6, 0x76, 0x44, 0xc8, 0x6b, + 0xcc, 0xe7, 0x8e, 0xeb, 0xf1, 0xd0, 0x95, 0x5e, 0xec, 0xa3, 0xbc, 0xe2, 0x48, 0x47, 0xea, 0x47, + 0x16, 0x3d, 0xe1, 0xe9, 0x65, 0x47, 0x4a, 0x67, 0x4f, 0x30, 0xee, 0xbb, 0x8c, 0x7b, 0x9e, 0x0c, + 0x35, 0x44, 0xe1, 0xed, 0xf5, 0x1c, 0x6e, 0x3d, 0x0e, 0xb1, 0x59, 0x35, 0xc7, 0x8c, 0x1f, 0x84, + 0x6d, 0x19, 0xb8, 0xdf, 0xa4, 0x69, 0x5c, 0xcd, 0xb1, 0xf5, 0x79, 0xc0, 0xf7, 0x31, 0xae, 0xb9, + 0x0d, 0xaf, 0x7f, 0x1a, 0xa9, 0xd9, 0x12, 0xa1, 0x8d, 0x36, 0xb6, 0x78, 0x7c, 0x20, 0x54, 0x48, + 0xef, 0xc0, 0xcc, 0xae, 0xe8, 0x94, 0x48, 0x85, 0xac, 0x2d, 0xd4, 0xaf, 0x5a, 0xa3, 0x13, 0x63, + 0x25, 0xa8, 0x87, 0xa2, 0x63, 0x47, 0xf6, 0x66, 0x13, 0x4a, 0xc3, 0x1e, 0x95, 0x2f, 0x3d, 0x25, + 0xe8, 0x16, 0xcc, 0x27, 0x58, 0xf4, 0x7b, 0xbd, 0xc8, 0xef, 0x87, 0x5e, 0x18, 0x74, 0x36, 0x2f, + 0x3c, 0xfb, 0xf7, 0xca, 0x94, 0xdd, 0x03, 0x9b, 0x4f, 0x08, 0xac, 0x0e, 0x44, 0x71, 0x85, 0x4a, + 0x98, 0x5f, 0x83, 0x25, 0xae, 0x94, 0x08, 0x1b, 0xcd, 0x3d, 0xae, 0x54, 0xc3, 0x6d, 0xe9, 0x60, + 0x2f, 0xdb, 0xaf, 0xe8, 0xd3, 0xfb, 0xd1, 0xe1, 0x83, 0x16, 0xfd, 0x08, 0xa0, 0x5f, 0xba, 0x52, + 0x53, 0xd3, 0xb9, 0x61, 0xc5, 0x75, 0xb6, 0xa2, 0x3a, 0x5b, 0x71, 0x63, 0x60, 0x9d, 0xad, 0x6d, + 0xee, 0x08, 0x8c, 0x60, 0xa7, 0x90, 0xe6, 0x2f, 0x04, 0xca, 0xa3, 0xb8, 0xa0, 0xe6, 0x87, 0x00, + 0x41, 0xef, 0xb4, 0x44, 0x2a, 0x33, 0x93, 0xaa, 0x4e, 0xc1, 0xe9, 0xd6, 0x08, 0xce, 0x6f, 0x15, + 0x72, 0x8e, 0x99, 0x64, 0x48, 0x3f, 0x25, 0x70, 0x49, 0x93, 0xfe, 0x98, 0x2b, 0x5b, 0xee, 0x89, + 0xb3, 0x15, 0x9d, 0x96, 0x60, 0x8e, 0xb7, 0x5a, 0x81, 0x50, 0xaa, 0x34, 0xad, 0x53, 0x9d, 0xbc, + 0xd2, 0xbb, 0x70, 0x21, 0x90, 0x7b, 0xa2, 0x34, 0x53, 0x21, 0x6b, 0x4b, 0xf5, 0x6b, 0x45, 0x1e, + 0x35, 0x17, 0x8d, 0x30, 0x6b, 0xb0, 0x92, 0x65, 0x88, 0x09, 0x5d, 0x85, 0xf9, 0x36, 0x57, 0x0d, + 0xed, 0x35, 0xe2, 0x39, 0x6f, 0xcf, 0xb5, 0x63, 0x13, 0x93, 0xc1, 0x9b, 0x1a, 0xb2, 0x2d, 0xbc, + 0x96, 0xeb, 0x39, 0xd1, 0xd9, 0xfd, 0x36, 0xf7, 0x7a, 0x75, 0xa3, 0x4b, 0x30, 0xdd, 0xeb, 0x86, + 0x69, 0xb7, 0x65, 0x7e, 0x4b, 0xc0, 0xc8, 0x43, 0x60, 0xb8, 0x06, 0x5c, 0xf2, 0xe3, 0x4b, 0x1d, + 0xb2, 0xd1, 0xd4, 0xd7, 0x98, 0xa1, 0xb7, 0xf3, 0xf4, 0x0c, 0xf9, 0xc3, 0x62, 0x2e, 0xfb, 0x83, + 0x17, 0xe6, 0x8f, 0xb9, 0x1c, 0xd4, 0x19, 0xab, 0x72, 0x5e, 0x1d, 0xfe, 0x17, 0x81, 0x2b, 0xb9, + 0x0c, 0x31, 0x4d, 0x1c, 0x56, 0x46, 0xa4, 0x29, 0x69, 0xf8, 0x89, 0xf3, 0x44, 0x87, 0xf2, 0x74, + 0x8e, 0xcd, 0xbf, 0x85, 0xc3, 0x23, 0x49, 0x98, 0x9e, 0x08, 0x49, 0xae, 0xab, 0xb0, 0x9c, 0x10, + 0x1c, 0x9c, 0x1f, 0x17, 0x83, 0x34, 0xe0, 0x41, 0xcb, 0xf4, 0xf1, 0x97, 0x3f, 0xe0, 0x08, 0x53, + 0x62, 0xc3, 0x52, 0xd6, 0xd3, 0xb8, 0x33, 0x4f, 0xbb, 0xc1, 0x44, 0x2c, 0x66, 0x62, 0x9a, 0x02, + 0xde, 0x18, 0x8e, 0xd8, 0x6f, 0x94, 0xf3, 0xaa, 0xf8, 0x1f, 0x04, 0x2e, 0x8f, 0x8e, 0x83, 0xda, + 0x3e, 0x87, 0x57, 0xb3, 0xda, 0xc6, 0x9f, 0x6d, 0x69, 0x75, 0xd9, 0x8c, 0x9e, 0x67, 0x8d, 0x57, + 0x80, 0xc6, 0x2d, 0xab, 0xb7, 0x1d, 0x6a, 0x34, 0x1f, 0xe1, 0xd4, 0x4b, 0x4e, 0x51, 0xcd, 0x3d, + 0x98, 0x8d, 0xb7, 0x22, 0x56, 0xc8, 0xc8, 0x6d, 0x57, 0x6d, 0x85, 0xe4, 0x11, 0x53, 0xff, 0x69, + 0x01, 0x5e, 0xd2, 0x5e, 0xe9, 0xef, 0x04, 0x16, 0x52, 0x7b, 0x8f, 0xb2, 0x3c, 0x3f, 0x39, 0x3b, + 0xb7, 0xbc, 0x3e, 0x3e, 0x20, 0xa6, 0x6e, 0x7e, 0xf2, 0xdd, 0xdf, 0xff, 0xff, 0x30, 0xfd, 0x01, + 0xdd, 0x64, 0x05, 0xff, 0x20, 0x58, 0x77, 0x57, 0x74, 0xac, 0xec, 0x5e, 0x3c, 0x8a, 0x0f, 0xbd, + 0xaf, 0xc2, 0xe8, 0x85, 0x3e, 0x25, 0xb0, 0x98, 0x59, 0x62, 0xb4, 0x36, 0x26, 0x9f, 0xfe, 0xf2, + 0x2d, 0xd7, 0x27, 0x81, 0xa0, 0x88, 0x35, 0x2d, 0xc2, 0xa4, 0x95, 0x22, 0x11, 0xf4, 0x4f, 0x02, + 0x73, 0xb8, 0x10, 0xe8, 0xcd, 0x53, 0x23, 0x65, 0x17, 0x5b, 0xf9, 0xd6, 0x78, 0xc6, 0x48, 0xe8, + 0x4b, 0x4d, 0xe8, 0x33, 0xfa, 0x28, 0x8f, 0x50, 0xb2, 0x81, 0x8a, 0xb3, 0xca, 0xba, 0xb8, 0x0a, + 0x8f, 0x58, 0x37, 0x42, 0x1c, 0x45, 0x5d, 0xb2, 0x3c, 0x34, 0xf7, 0xe8, 0x9d, 0x53, 0x09, 0xe6, + 0x6d, 0xb4, 0xf2, 0xbb, 0x93, 0xc2, 0x50, 0xe1, 0x5d, 0xad, 0xb0, 0x4e, 0xd7, 0xf3, 0x14, 0x8e, + 0x98, 0xe6, 0xac, 0x1b, 0x75, 0xc9, 0x6f, 0x04, 0xe8, 0xf0, 0x22, 0xa0, 0x13, 0x12, 0xe9, 0xf5, + 0xcb, 0x7b, 0x13, 0xe3, 0x50, 0xc1, 0x86, 0x56, 0x60, 0xd1, 0x5b, 0x13, 0x28, 0x50, 0xf4, 0x57, + 0x02, 0x8b, 0x99, 0x49, 0x54, 0xd0, 0xe3, 0xa3, 0x76, 0x44, 0x41, 0x8f, 0x8f, 0xdc, 0x06, 0xe6, + 0xa6, 0xa6, 0x7b, 0x8f, 0xbe, 0x5f, 0xd4, 0xe3, 0x71, 0x1f, 0xb1, 0xee, 0xd0, 0x16, 0x3a, 0xa2, + 0x3f, 0x13, 0xb8, 0x38, 0x30, 0x91, 0xe9, 0xed, 0xf1, 0xb9, 0xf4, 0x93, 0xbe, 0x31, 0x19, 0x08, + 0x25, 0xac, 0x6b, 0x09, 0x55, 0xba, 0x36, 0x9e, 0x04, 0xa1, 0xe8, 0x13, 0x02, 0xb3, 0xf1, 0xcc, + 0xa4, 0xd5, 0xd3, 0xeb, 0x9c, 0x1e, 0xd3, 0xe5, 0x9b, 0x63, 0xd9, 0x22, 0xab, 0x1b, 0x9a, 0x55, + 0x85, 0x1a, 0xec, 0xd4, 0x0f, 0x9e, 0xcd, 0xdd, 0x67, 0xc7, 0x06, 0x79, 0x7e, 0x6c, 0x90, 0xff, + 0x8e, 0x0d, 0xf2, 0xfd, 0x89, 0x31, 0xf5, 0xfc, 0xc4, 0x98, 0xfa, 0xe7, 0xc4, 0x98, 0x82, 0x55, + 0x57, 0xe6, 0x04, 0xdc, 0x26, 0x5f, 0x6c, 0x38, 0x6e, 0xd8, 0x3e, 0xd8, 0xb1, 0x9a, 0x72, 0x3f, + 0x15, 0xe0, 0x1d, 0x57, 0xa6, 0xc3, 0x7d, 0xdd, 0x0f, 0x18, 0x76, 0x7c, 0xa1, 0x76, 0x66, 0xf5, + 0xe7, 0xd5, 0xed, 0x17, 0x01, 0x00, 0x00, 0xff, 0xff, 0x55, 0x6b, 0x3d, 0x08, 0x74, 0x0e, 0x00, + 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -864,6 +954,9 @@ type QueryClient interface { RegistryClass(ctx context.Context, in *QueryRegistryClassRequest, opts ...grpc.CallOption) (*QueryRegistryClassResponse, error) // RegistryClasses returns all registry classes. RegistryClasses(ctx context.Context, in *QueryRegistryClassesRequest, opts ...grpc.CallOption) (*QueryRegistryClassesResponse, error) + // Params returns the registry module parameters, including the default role authorization + // policies. + Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) } type queryClient struct { @@ -937,6 +1030,15 @@ func (c *queryClient) RegistryClasses(ctx context.Context, in *QueryRegistryClas return out, nil } +func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) { + out := new(QueryParamsResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // GetRegistry returns the registry entry for a given key. @@ -955,6 +1057,9 @@ type QueryServer interface { RegistryClass(context.Context, *QueryRegistryClassRequest) (*QueryRegistryClassResponse, error) // RegistryClasses returns all registry classes. RegistryClasses(context.Context, *QueryRegistryClassesRequest) (*QueryRegistryClassesResponse, error) + // Params returns the registry module parameters, including the default role authorization + // policies. + Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -982,6 +1087,9 @@ func (*UnimplementedQueryServer) RegistryClass(ctx context.Context, req *QueryRe func (*UnimplementedQueryServer) RegistryClasses(ctx context.Context, req *QueryRegistryClassesRequest) (*QueryRegistryClassesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method RegistryClasses not implemented") } +func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -1113,6 +1221,24 @@ func _Query_RegistryClasses_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*QueryParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + var Query_serviceDesc = _Query_serviceDesc var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Query", @@ -1146,6 +1272,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "RegistryClasses", Handler: _Query_RegistryClasses_Handler, }, + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/query.proto", @@ -1708,6 +1838,62 @@ func (m *QueryRegistryClassesResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } +func (m *QueryParamsRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *QueryParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1927,6 +2113,26 @@ func (m *QueryRegistryClassesResponse) Size() (n int) { return n } +func (m *QueryParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *QueryParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3325,6 +3531,139 @@ func (m *QueryRegistryClassesResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryParamsRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/query.pb.gw.go b/x/registry/types/query.pb.gw.go index 589d756bde..ff3737e139 100644 --- a/x/registry/types/query.pb.gw.go +++ b/x/registry/types/query.pb.gw.go @@ -487,6 +487,24 @@ func local_request_Query_RegistryClasses_0(ctx context.Context, marshaler runtim } +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -654,6 +672,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -835,6 +876,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -852,6 +913,8 @@ var ( pattern_Query_RegistryClass_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"provenance", "registry", "v1", "registry_class", "registry_class_id"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_RegistryClasses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "registry_classes"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -868,4 +931,6 @@ var ( forward_Query_RegistryClass_0 = runtime.ForwardResponseMessage forward_Query_RegistryClasses_0 = runtime.ForwardResponseMessage + + forward_Query_Params_0 = runtime.ForwardResponseMessage ) diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 83f379628f..632b41357f 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -1136,6 +1136,98 @@ func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgUpdateRegistryClassRoleAuthorizationResponse proto.InternalMessageInfo +// MsgUpdateParams is a request to update the registry module's params. +type MsgUpdateParams struct { + // authority should be the governance module account address. + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // params are the new registry module params to set. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{20} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse defines the response for UpdateParams. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{21} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgRegisterNFT)(nil), "provenance.registry.v1.MsgRegisterNFT") proto.RegisterType((*MsgRegisterNFTResponse)(nil), "provenance.registry.v1.MsgRegisterNFTResponse") @@ -1157,74 +1249,80 @@ func init() { proto.RegisterType((*MsgCreateRegistryClassResponse)(nil), "provenance.registry.v1.MsgCreateRegistryClassResponse") proto.RegisterType((*MsgUpdateRegistryClassRoleAuthorization)(nil), "provenance.registry.v1.MsgUpdateRegistryClassRoleAuthorization") proto.RegisterType((*MsgUpdateRegistryClassRoleAuthorizationResponse)(nil), "provenance.registry.v1.MsgUpdateRegistryClassRoleAuthorizationResponse") + proto.RegisterType((*MsgUpdateParams)(nil), "provenance.registry.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "provenance.registry.v1.MsgUpdateParamsResponse") } func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 978 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x97, 0x41, 0x6f, 0x1b, 0x45, - 0x14, 0xc7, 0x33, 0xb6, 0xd3, 0xc6, 0xcf, 0x69, 0xa0, 0x9b, 0x34, 0xdd, 0x2c, 0xd4, 0xb1, 0xdc, - 0x04, 0x4c, 0x68, 0xbc, 0x4d, 0x68, 0xab, 0x88, 0x03, 0x28, 0x89, 0x4a, 0x15, 0x55, 0x46, 0xd5, - 0x96, 0x5c, 0x10, 0x92, 0xd9, 0xd8, 0xa3, 0xcd, 0xca, 0xf1, 0xce, 0x6a, 0x66, 0x12, 0xea, 0x4a, - 0x48, 0x88, 0x13, 0x47, 0xf8, 0x00, 0x9c, 0xf8, 0x02, 0x39, 0xf0, 0x15, 0x90, 0x7a, 0x8c, 0x38, - 0xc1, 0xa5, 0x42, 0xc9, 0xa1, 0x12, 0x1f, 0x00, 0xae, 0x68, 0x67, 0x77, 0xc7, 0xbb, 0xf6, 0x7a, - 0xbd, 0x69, 0xd5, 0x20, 0xc1, 0xcd, 0xbb, 0xf3, 0x9b, 0x79, 0xff, 0xff, 0xf3, 0x9b, 0x79, 0xb3, - 0xb0, 0xe8, 0x52, 0x72, 0x84, 0x1d, 0xd3, 0x69, 0x61, 0x9d, 0x62, 0xcb, 0x66, 0x9c, 0xf6, 0xf4, - 0xa3, 0x35, 0x9d, 0x3f, 0xa9, 0xbb, 0x94, 0x70, 0xa2, 0xcc, 0xf7, 0x81, 0x7a, 0x08, 0xd4, 0x8f, - 0xd6, 0xb4, 0x39, 0x8b, 0x58, 0x44, 0x20, 0xba, 0xf7, 0xcb, 0xa7, 0xb5, 0x85, 0x16, 0x61, 0x5d, - 0xc2, 0x9a, 0xfe, 0x80, 0xff, 0x10, 0x0c, 0x5d, 0xf7, 0x9f, 0xf4, 0x2e, 0xb3, 0xbc, 0x00, 0x5d, - 0x66, 0x05, 0x03, 0xcb, 0x23, 0x24, 0xc8, 0x68, 0x3e, 0xb6, 0x32, 0x02, 0x33, 0x0f, 0xf9, 0x3e, - 0xa1, 0xf6, 0x53, 0x93, 0xdb, 0xc4, 0xf1, 0xd9, 0xea, 0x0f, 0x39, 0x98, 0x69, 0x30, 0xcb, 0x10, - 0x18, 0xa6, 0x9f, 0x7e, 0xf2, 0x99, 0x72, 0x1b, 0x2e, 0x31, 0xdb, 0x72, 0x30, 0x55, 0x51, 0x05, - 0xd5, 0x8a, 0x5b, 0xea, 0xaf, 0x3f, 0xaf, 0xce, 0x05, 0x02, 0x37, 0xdb, 0x6d, 0x8a, 0x19, 0x7b, - 0xcc, 0xa9, 0xed, 0x58, 0x46, 0xc0, 0x29, 0x77, 0x21, 0xdf, 0xc1, 0x3d, 0x35, 0x57, 0x41, 0xb5, - 0xd2, 0xfa, 0xcd, 0x7a, 0x72, 0x1e, 0xea, 0x46, 0xf0, 0xfb, 0x21, 0xee, 0x19, 0x1e, 0xaf, 0x7c, - 0x04, 0x93, 0x94, 0x1c, 0x60, 0xa6, 0xe6, 0x2b, 0xf9, 0x5a, 0x69, 0xbd, 0x3a, 0x72, 0xa2, 0x07, - 0xdd, 0x77, 0x38, 0xed, 0x6d, 0x15, 0x9e, 0x3d, 0x5f, 0x9c, 0x30, 0xfc, 0x69, 0xca, 0x0e, 0x5c, - 0x0d, 0xb1, 0x66, 0xeb, 0xc0, 0x64, 0xac, 0x69, 0xb7, 0xd5, 0x82, 0xd0, 0x7c, 0xe3, 0xcf, 0xe7, - 0x8b, 0x0b, 0xe1, 0xe0, 0xb6, 0x37, 0xb6, 0xd3, 0xbe, 0x45, 0xba, 0x36, 0xc7, 0x5d, 0x97, 0xf7, - 0x8c, 0x37, 0x06, 0x86, 0x3e, 0x2c, 0x7d, 0xfb, 0xe2, 0x78, 0x25, 0xb0, 0x53, 0x55, 0x61, 0x3e, - 0x9e, 0x12, 0x03, 0x33, 0x97, 0x38, 0x0c, 0x57, 0xff, 0x42, 0x30, 0xdd, 0x60, 0xd6, 0x03, 0x6a, - 0x3a, 0xdc, 0x53, 0x75, 0x71, 0xb9, 0xda, 0x80, 0x82, 0x67, 0x5a, 0xcd, 0x57, 0x50, 0x6d, 0x66, - 0x7d, 0x69, 0xdc, 0x3c, 0x4f, 0x9c, 0x21, 0x66, 0x28, 0xf7, 0xa0, 0x68, 0xfa, 0x4a, 0x30, 0x53, - 0x0b, 0x95, 0x7c, 0xaa, 0xca, 0x3e, 0x1a, 0x4f, 0xc9, 0x3c, 0xcc, 0x45, 0x7d, 0xcb, 0x84, 0xfc, - 0x8d, 0xe0, 0x8a, 0xc8, 0xd5, 0x11, 0xe9, 0xe0, 0xff, 0x55, 0x46, 0xae, 0xc3, 0xb5, 0x98, 0x71, - 0x99, 0x92, 0xef, 0x10, 0xbc, 0xd9, 0x60, 0xd6, 0xae, 0x43, 0xff, 0x85, 0x3d, 0x15, 0xd7, 0xa8, - 0x81, 0x3a, 0xa8, 0x44, 0xca, 0xfc, 0x11, 0x05, 0x06, 0xfc, 0x05, 0xb6, 0x0e, 0x0f, 0x3a, 0xbb, - 0x6e, 0xdb, 0xe4, 0x2f, 0xf3, 0x0f, 0xde, 0x87, 0xcb, 0xd8, 0xe1, 0xd4, 0xc6, 0x4c, 0xcd, 0x89, - 0xad, 0xbc, 0x3c, 0x4e, 0x6f, 0x74, 0x37, 0x87, 0x73, 0xe3, 0xda, 0x17, 0xe1, 0x46, 0xa2, 0x3c, - 0x69, 0xe0, 0x04, 0x41, 0xa9, 0xc1, 0xac, 0xc7, 0x58, 0x54, 0x24, 0xbb, 0xb8, 0xc2, 0x7b, 0x08, - 0xd3, 0x5e, 0x19, 0x35, 0x0f, 0x85, 0x9e, 0x4c, 0xa7, 0x97, 0x2f, 0x3d, 0xf0, 0x5b, 0xa2, 0xf2, - 0xcd, 0x80, 0xe7, 0x6b, 0x30, 0x1b, 0x71, 0x24, 0x9d, 0xfe, 0x8e, 0xc4, 0xee, 0x7b, 0x44, 0x89, - 0x4b, 0x98, 0x28, 0xb6, 0xed, 0x7d, 0xd3, 0xb1, 0xf0, 0x7f, 0xc1, 0xf2, 0x2e, 0xbc, 0x9d, 0x64, - 0x2d, 0xf4, 0xae, 0xbc, 0x05, 0xc5, 0x96, 0x78, 0xe3, 0x9d, 0xed, 0xc2, 0xa5, 0x31, 0xe5, 0xbf, - 0xd8, 0x69, 0x2b, 0x2a, 0x5c, 0x36, 0x5d, 0xf7, 0xc0, 0xc6, 0x6d, 0xe1, 0x68, 0xca, 0x08, 0x1f, - 0xab, 0x54, 0x64, 0x6c, 0xd3, 0x15, 0x02, 0x5f, 0x29, 0x63, 0x31, 0x01, 0xb9, 0xb8, 0x80, 0xb8, - 0x95, 0x0d, 0x61, 0x65, 0x28, 0xa6, 0xb4, 0x12, 0x51, 0x8b, 0xe2, 0x6a, 0x7f, 0xc9, 0x89, 0x8e, - 0xb3, 0x4d, 0xb1, 0x28, 0xf0, 0x48, 0x6b, 0x7a, 0x09, 0xc1, 0x2b, 0x49, 0x5d, 0xd1, 0x17, 0x3e, - 0xd8, 0xf6, 0x94, 0x25, 0x98, 0x31, 0x19, 0xc3, 0xbc, 0x0f, 0xe6, 0x05, 0x38, 0x2d, 0xde, 0x86, - 0xd4, 0x06, 0x40, 0xd7, 0xb4, 0x1d, 0x6e, 0xda, 0x9e, 0x8e, 0xc2, 0x18, 0x1d, 0x11, 0x56, 0xf9, - 0x12, 0x66, 0x45, 0xdd, 0xc4, 0x6e, 0x1e, 0x4c, 0x9d, 0x14, 0xe5, 0xf3, 0x5e, 0x5a, 0xf9, 0x6c, - 0x46, 0x67, 0x04, 0x55, 0xa4, 0xd0, 0xc1, 0x81, 0x81, 0x62, 0xaa, 0x40, 0x39, 0x39, 0x8d, 0xd1, - 0x06, 0xfe, 0xae, 0x77, 0x24, 0x06, 0x47, 0x49, 0x14, 0x19, 0x5c, 0xfb, 0x35, 0xa7, 0x7e, 0x44, - 0x6a, 0xf2, 0xaf, 0x29, 0x35, 0x6b, 0xa0, 0x67, 0xf4, 0x1d, 0xe6, 0x6a, 0xfd, 0xa7, 0x22, 0xe4, - 0x1b, 0xcc, 0x52, 0x30, 0x94, 0xa2, 0xd7, 0xc3, 0x77, 0x46, 0x69, 0x8b, 0xdf, 0x99, 0xb4, 0x7a, - 0x36, 0x4e, 0x6e, 0x8f, 0x26, 0x14, 0xfb, 0xf7, 0xaa, 0xa5, 0x94, 0xc9, 0x92, 0xd2, 0x6e, 0x65, - 0xa1, 0x64, 0x80, 0x3d, 0x80, 0xc8, 0x3d, 0x65, 0x39, 0x55, 0x5e, 0x88, 0x69, 0xab, 0x99, 0x30, - 0x19, 0xa3, 0x03, 0x57, 0xe2, 0x8d, 0xbf, 0x96, 0x32, 0x3f, 0x46, 0x6a, 0xb7, 0xb3, 0x92, 0x32, - 0xd8, 0x53, 0x50, 0x12, 0xda, 0xf7, 0xea, 0xd8, 0xbc, 0x47, 0x71, 0xed, 0xee, 0xb9, 0x70, 0x19, - 0xfb, 0x0b, 0x98, 0x92, 0x9d, 0xf7, 0x66, 0xca, 0x12, 0x21, 0xa4, 0xbd, 0x9f, 0x01, 0x92, 0xab, - 0x7f, 0x05, 0x57, 0x87, 0xbb, 0x5d, 0xda, 0xbf, 0x3d, 0x44, 0x6b, 0x77, 0xce, 0x43, 0x47, 0x03, - 0x0f, 0x37, 0x8d, 0xb4, 0xc0, 0x43, 0x74, 0x6a, 0xe0, 0xd1, 0xcd, 0xe1, 0x6b, 0x98, 0x4d, 0x3a, - 0xfe, 0xd3, 0x36, 0x51, 0x02, 0xaf, 0xdd, 0x3b, 0x1f, 0x2f, 0xc3, 0x1f, 0x23, 0x58, 0xca, 0x74, - 0x28, 0x7e, 0x9c, 0x56, 0xa5, 0x19, 0x16, 0xd0, 0x1e, 0xbc, 0xe2, 0x02, 0xa1, 0x64, 0x6d, 0xf2, - 0x9b, 0x17, 0xc7, 0x2b, 0x68, 0xab, 0xf3, 0xec, 0xb4, 0x8c, 0x4e, 0x4e, 0xcb, 0xe8, 0x8f, 0xd3, - 0x32, 0xfa, 0xfe, 0xac, 0x3c, 0x71, 0x72, 0x56, 0x9e, 0xf8, 0xed, 0xac, 0x3c, 0x01, 0x0b, 0x36, - 0x19, 0x11, 0xeb, 0x11, 0xfa, 0xfc, 0x8e, 0x65, 0xf3, 0xfd, 0xc3, 0xbd, 0x7a, 0x8b, 0x74, 0xf5, - 0x3e, 0xb4, 0x6a, 0x93, 0xc8, 0x93, 0xfe, 0xa4, 0xff, 0xf9, 0xcc, 0x7b, 0x2e, 0x66, 0x7b, 0x97, - 0xc4, 0x47, 0xf3, 0x07, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x12, 0x27, 0xcd, 0xcb, 0x0c, 0x10, - 0x00, 0x00, + // 1053 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x98, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0x33, 0xb6, 0x93, 0x26, 0xcf, 0x69, 0x4a, 0x37, 0x69, 0xb2, 0x59, 0xa8, 0x63, 0x39, + 0x09, 0x35, 0xa1, 0xb1, 0x9b, 0xd0, 0x56, 0x51, 0x85, 0x40, 0x49, 0x54, 0xaa, 0xa8, 0x32, 0x8a, + 0xb6, 0xe4, 0x82, 0x90, 0xcc, 0xc6, 0x1e, 0x6d, 0x56, 0x8e, 0x77, 0x56, 0x33, 0x93, 0x50, 0x57, + 0x42, 0x42, 0x9c, 0xb8, 0x20, 0xc1, 0x89, 0x13, 0xdf, 0x21, 0x07, 0xbe, 0x02, 0x52, 0x8f, 0x11, + 0x27, 0xb8, 0x54, 0x28, 0x39, 0x54, 0xe2, 0x03, 0xc0, 0x15, 0xed, 0xec, 0xee, 0x78, 0xd7, 0xf6, + 0xae, 0x37, 0xad, 0x5a, 0x24, 0x7a, 0xf3, 0xee, 0xfc, 0x66, 0xde, 0xff, 0xff, 0xfc, 0x66, 0xe6, + 0x69, 0x61, 0xc1, 0xa1, 0xe4, 0x18, 0xdb, 0x86, 0xdd, 0xc0, 0x55, 0x8a, 0x4d, 0x8b, 0x71, 0xda, + 0xa9, 0x1e, 0xaf, 0x55, 0xf9, 0xe3, 0x8a, 0x43, 0x09, 0x27, 0xca, 0x6c, 0x17, 0xa8, 0x04, 0x40, + 0xe5, 0x78, 0x4d, 0x9b, 0x31, 0x89, 0x49, 0x04, 0x52, 0x75, 0x7f, 0x79, 0xb4, 0x36, 0xdf, 0x20, + 0xac, 0x4d, 0x58, 0xdd, 0x1b, 0xf0, 0x1e, 0xfc, 0xa1, 0x39, 0xef, 0xa9, 0xda, 0x66, 0xa6, 0x1b, + 0xa0, 0xcd, 0x4c, 0x7f, 0x60, 0x39, 0x46, 0x82, 0x8c, 0xe6, 0x61, 0x2b, 0x31, 0x98, 0x71, 0xc4, + 0x0f, 0x08, 0xb5, 0x9e, 0x18, 0xdc, 0x22, 0xb6, 0xcf, 0x2e, 0xc6, 0xb0, 0x8e, 0x41, 0x8d, 0xb6, + 0x2f, 0xa8, 0xf4, 0x63, 0x06, 0xa6, 0x6a, 0xcc, 0xd4, 0xc5, 0x38, 0xa6, 0x9f, 0x7e, 0xf2, 0x99, + 0x72, 0x0b, 0xc6, 0x98, 0x65, 0xda, 0x98, 0xaa, 0xa8, 0x88, 0xca, 0x13, 0x5b, 0xea, 0x6f, 0xbf, + 0xac, 0xce, 0xf8, 0x2e, 0x36, 0x9b, 0x4d, 0x8a, 0x19, 0x7b, 0xc4, 0xa9, 0x65, 0x9b, 0xba, 0xcf, + 0x29, 0x77, 0x20, 0xdb, 0xc2, 0x1d, 0x35, 0x53, 0x44, 0xe5, 0xfc, 0xfa, 0x62, 0x65, 0x70, 0xb2, + 0x2a, 0xba, 0xff, 0xfb, 0x21, 0xee, 0xe8, 0x2e, 0xaf, 0x7c, 0x04, 0xa3, 0x94, 0x1c, 0x62, 0xa6, + 0x66, 0x8b, 0xd9, 0x72, 0x7e, 0xbd, 0x14, 0x3b, 0xd1, 0x85, 0xee, 0xdb, 0x9c, 0x76, 0xb6, 0x72, + 0x4f, 0x9f, 0x2d, 0x8c, 0xe8, 0xde, 0x34, 0x65, 0x07, 0xae, 0x06, 0x58, 0xbd, 0x71, 0x68, 0x30, + 0x56, 0xb7, 0x9a, 0x6a, 0x4e, 0x68, 0xbe, 0xfe, 0xd7, 0xb3, 0x85, 0xf9, 0x60, 0x70, 0xdb, 0x1d, + 0xdb, 0x69, 0xde, 0x24, 0x6d, 0x8b, 0xe3, 0xb6, 0xc3, 0x3b, 0xfa, 0x95, 0x9e, 0xa1, 0x7b, 0xf9, + 0x6f, 0x9f, 0x9f, 0xac, 0xf8, 0x76, 0x4a, 0x2a, 0xcc, 0x46, 0x53, 0xa2, 0x63, 0xe6, 0x10, 0x9b, + 0xe1, 0xd2, 0xdf, 0x08, 0x26, 0x6b, 0xcc, 0x7c, 0x40, 0x0d, 0x9b, 0xbb, 0xaa, 0x5e, 0x5f, 0xae, + 0x36, 0x20, 0xe7, 0x9a, 0x56, 0xb3, 0x45, 0x54, 0x9e, 0x5a, 0x5f, 0x1a, 0x36, 0xcf, 0x15, 0xa7, + 0x8b, 0x19, 0xca, 0x5d, 0x98, 0x30, 0x3c, 0x25, 0x98, 0xa9, 0xb9, 0x62, 0x36, 0x51, 0x65, 0x17, + 0x8d, 0xa6, 0x64, 0x16, 0x66, 0xc2, 0xbe, 0x65, 0x42, 0xfe, 0x41, 0x70, 0x59, 0xe4, 0xea, 0x98, + 0xb4, 0xf0, 0x1b, 0x95, 0x91, 0x39, 0xb8, 0x16, 0x31, 0x2e, 0x53, 0xf2, 0x1d, 0x82, 0xb7, 0x6a, + 0xcc, 0xdc, 0xb3, 0xe9, 0x7f, 0xb0, 0xa7, 0xa2, 0x1a, 0x35, 0x50, 0x7b, 0x95, 0x48, 0x99, 0x3f, + 0x23, 0xdf, 0x80, 0xb7, 0xc0, 0xd6, 0xd1, 0x61, 0x6b, 0xcf, 0x69, 0x1a, 0xfc, 0x45, 0xfe, 0xc1, + 0xfb, 0x70, 0x09, 0xdb, 0x9c, 0x5a, 0x98, 0xa9, 0x19, 0xb1, 0x95, 0x97, 0x87, 0xe9, 0x0d, 0xef, + 0xe6, 0x60, 0x6e, 0x54, 0xfb, 0x02, 0x5c, 0x1f, 0x28, 0x4f, 0x1a, 0x38, 0x45, 0x90, 0xaf, 0x31, + 0xf3, 0x11, 0x16, 0x15, 0xc9, 0x5e, 0x5f, 0xe1, 0x3d, 0x84, 0x49, 0xb7, 0x8c, 0xea, 0x47, 0x42, + 0x4f, 0xaa, 0xd3, 0xcb, 0x93, 0xee, 0xfb, 0xcd, 0x53, 0xf9, 0xa6, 0xc7, 0xf3, 0x35, 0x98, 0x0e, + 0x39, 0x92, 0x4e, 0xff, 0x40, 0x62, 0xf7, 0xed, 0x52, 0xe2, 0x10, 0x26, 0x8a, 0x6d, 0xfb, 0xc0, + 0xb0, 0x4d, 0xfc, 0x7f, 0xb0, 0xbc, 0x07, 0xef, 0x0c, 0xb2, 0x16, 0x78, 0x57, 0xde, 0x86, 0x89, + 0x86, 0x78, 0xe3, 0x9e, 0xed, 0xc2, 0xa5, 0x3e, 0xee, 0xbd, 0xd8, 0x69, 0x2a, 0x2a, 0x5c, 0x32, + 0x1c, 0xe7, 0xd0, 0xc2, 0x4d, 0xe1, 0x68, 0x5c, 0x0f, 0x1e, 0x4b, 0x54, 0x64, 0x6c, 0xd3, 0x11, + 0x02, 0x5f, 0x2a, 0x63, 0x11, 0x01, 0x99, 0xa8, 0x80, 0xa8, 0x95, 0x0d, 0x61, 0xa5, 0x2f, 0xa6, + 0xb4, 0x12, 0x52, 0x8b, 0xa2, 0x6a, 0x7f, 0xcd, 0x88, 0x1b, 0x67, 0x9b, 0x62, 0x51, 0xe0, 0xa1, + 0xab, 0xe9, 0x05, 0x04, 0xaf, 0x0c, 0xba, 0x15, 0x3d, 0xe1, 0xbd, 0xd7, 0x9e, 0xb2, 0x04, 0x53, + 0x06, 0x63, 0x98, 0x77, 0xc1, 0xac, 0x00, 0x27, 0xc5, 0xdb, 0x80, 0xda, 0x00, 0x68, 0x1b, 0x96, + 0xcd, 0x0d, 0xcb, 0xd5, 0x91, 0x1b, 0xa2, 0x23, 0xc4, 0x2a, 0x5f, 0xc2, 0xb4, 0xa8, 0x9b, 0x48, + 0x7b, 0xc2, 0xd4, 0x51, 0x51, 0x3e, 0xef, 0x25, 0x95, 0xcf, 0x66, 0x78, 0x86, 0x5f, 0x45, 0x0a, + 0xed, 0x1d, 0xe8, 0x29, 0xa6, 0x22, 0x14, 0x06, 0xa7, 0x31, 0x7c, 0x81, 0xdf, 0x70, 0x8f, 0x44, + 0xff, 0x28, 0x09, 0x23, 0xbd, 0x6b, 0xbf, 0xe2, 0xd4, 0xc7, 0xa4, 0x26, 0xfb, 0x8a, 0x52, 0xb3, + 0x06, 0xd5, 0x94, 0xbe, 0x65, 0xae, 0x7e, 0x42, 0x70, 0x45, 0xce, 0xd9, 0x15, 0x4d, 0xa3, 0xb8, + 0x3a, 0x3d, 0x98, 0x77, 0x86, 0xa6, 0xa5, 0x8b, 0x2a, 0x1f, 0xc2, 0x98, 0xd7, 0x76, 0xfa, 0x47, + 0x4f, 0x21, 0xce, 0xa0, 0x17, 0xc7, 0x77, 0xe5, 0xcf, 0xb9, 0x37, 0xe5, 0x3a, 0xe9, 0xae, 0x56, + 0x9a, 0x87, 0xb9, 0x1e, 0x61, 0x81, 0xe8, 0xf5, 0xef, 0x01, 0xb2, 0x35, 0x66, 0x2a, 0x18, 0xf2, + 0xe1, 0x9e, 0xf6, 0xdd, 0xb8, 0x78, 0xd1, 0x46, 0x4f, 0xab, 0xa4, 0xe3, 0xe4, 0x9e, 0xae, 0xc3, + 0x44, 0xb7, 0x19, 0x5c, 0x4a, 0x98, 0x2c, 0x29, 0xed, 0x66, 0x1a, 0x4a, 0x06, 0xd8, 0x07, 0x08, + 0x35, 0x57, 0xcb, 0x89, 0xf2, 0x02, 0x4c, 0x5b, 0x4d, 0x85, 0xc9, 0x18, 0x2d, 0xb8, 0x1c, 0xed, + 0x56, 0xca, 0x09, 0xf3, 0x23, 0xa4, 0x76, 0x2b, 0x2d, 0x29, 0x83, 0x3d, 0x01, 0x65, 0x40, 0xcf, + 0xb1, 0x3a, 0x34, 0xef, 0x61, 0x5c, 0xbb, 0x73, 0x21, 0x5c, 0xc6, 0xfe, 0x02, 0xc6, 0x65, 0xbb, + 0xb0, 0x98, 0xb0, 0x44, 0x00, 0x69, 0xef, 0xa7, 0x80, 0xe4, 0xea, 0x5f, 0xc1, 0xd5, 0xfe, 0x2b, + 0x3a, 0xe9, 0xdf, 0xee, 0xa3, 0xb5, 0xdb, 0x17, 0xa1, 0xc3, 0x81, 0xfb, 0x6f, 0xba, 0xa4, 0xc0, + 0x7d, 0x74, 0x62, 0xe0, 0xf8, 0x1b, 0xed, 0x6b, 0x98, 0x1e, 0x74, 0x67, 0x25, 0x6d, 0xa2, 0x01, + 0xbc, 0x76, 0xf7, 0x62, 0xbc, 0x0c, 0x7f, 0x82, 0x60, 0x29, 0xd5, 0x49, 0xfe, 0x71, 0x52, 0x95, + 0xa6, 0x58, 0x40, 0x7b, 0xf0, 0x92, 0x0b, 0x48, 0xc9, 0x07, 0x30, 0x19, 0x39, 0x4f, 0x6f, 0x0c, + 0x5d, 0xd8, 0x03, 0xb5, 0x6a, 0x4a, 0x30, 0x88, 0xa4, 0x8d, 0x7e, 0xf3, 0xfc, 0x64, 0x05, 0x6d, + 0xb5, 0x9e, 0x9e, 0x15, 0xd0, 0xe9, 0x59, 0x01, 0xfd, 0x79, 0x56, 0x40, 0x3f, 0x9c, 0x17, 0x46, + 0x4e, 0xcf, 0x0b, 0x23, 0xbf, 0x9f, 0x17, 0x46, 0x60, 0xde, 0x22, 0x31, 0x6b, 0xee, 0xa2, 0xcf, + 0x6f, 0x9b, 0x16, 0x3f, 0x38, 0xda, 0xaf, 0x34, 0x48, 0xbb, 0xda, 0x85, 0x56, 0x2d, 0x12, 0x7a, + 0xaa, 0x3e, 0xee, 0x7e, 0x56, 0xe0, 0x1d, 0x07, 0xb3, 0xfd, 0x31, 0xf1, 0x4d, 0xe1, 0x83, 0x7f, + 0x03, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x49, 0xd0, 0x16, 0x50, 0x11, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1275,6 +1373,10 @@ type MsgClient interface { // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry // class. Only the current maintainer may update the rules. UpdateRegistryClassRoleAuthorization(ctx context.Context, in *MsgUpdateRegistryClassRoleAuthorization, opts ...grpc.CallOption) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) + // UpdateParams is a governance proposal endpoint for updating the registry module's params, + // including the default role authorization policies. The authority must be the governance module + // account address. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) } type msgClient struct { @@ -1375,6 +1477,15 @@ func (c *msgClient) UpdateRegistryClassRoleAuthorization(ctx context.Context, in return out, nil } +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // RegisterNFT registers a new NFT in the registry. @@ -1413,6 +1524,10 @@ type MsgServer interface { // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry // class. Only the current maintainer may update the rules. UpdateRegistryClassRoleAuthorization(context.Context, *MsgUpdateRegistryClassRoleAuthorization) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) + // UpdateParams is a governance proposal endpoint for updating the registry module's params, + // including the default role authorization policies. The authority must be the governance module + // account address. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -1449,6 +1564,9 @@ func (*UnimplementedMsgServer) CreateRegistryClass(ctx context.Context, req *Msg func (*UnimplementedMsgServer) UpdateRegistryClassRoleAuthorization(ctx context.Context, req *MsgUpdateRegistryClassRoleAuthorization) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistryClassRoleAuthorization not implemented") } +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -1634,6 +1752,24 @@ func _Msg_UpdateRegistryClassRoleAuthorization_Handler(srv interface{}, ctx cont return interceptor(ctx, in, info, handler) } +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + var Msg_serviceDesc = _Msg_serviceDesc var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Msg", @@ -1679,6 +1815,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateRegistryClassRoleAuthorization", Handler: _Msg_UpdateRegistryClassRoleAuthorization_Handler, }, + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/tx.proto", @@ -2467,6 +2607,69 @@ func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) MarshalToSizedBuffer(d return len(dAtA) - i, nil } +func (m *MsgUpdateParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -2810,6 +3013,30 @@ func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) Size() (n int) { return n } +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -4922,6 +5149,171 @@ func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) Unmarshal(dAtA []byte) } return nil } +func (m *MsgUpdateParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 From b8094c47970eeb037e33e0c082ea6bf8f9cc38e9 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Wed, 24 Jun 2026 16:29:17 -0600 Subject: [PATCH 17/36] feat(registry): validate DART role policies as registry-class data (Phase C3) The ticket's per-role authorization policies (Originator, Lien Owner, Controller, Secured Party for Lien/eNote, Pledgee, Servicer, Sub-servicer) are user/governance-created RegistryClass role_authorizations data, not hard-coded chain logic. The policy engine already supports every construct they use, including the unilateral foreclosure path. So C3 adds validation and tests/examples rather than new role logic: - Add Validate() for SignatureType, NftRole, Assignment, and Assignment.IsCurrent(); deepen validateRoleAuthorizations to reject malformed authorization paths at create time (unknown/unspecified enums, missing selectors, nft_role with a NEW assignment, empty paths) instead of only failing closed at evaluation time. - Enforce class.Validate() on the registry-class create/update write path (defense-in-depth beyond ValidateBasic). - Add DART role-policy acceptance tests covering each role's consent matrix, the Lien Owner and Controller foreclosure paths, and malformed-policy rejection. - Ship x/registry/spec/examples/dart_registry_class.json (a complete DART registry class) kept in sync with the test builders. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/class.go | 7 + .../dart_role_policies_acceptance_test.go | 665 ++++++++++++++++++ x/registry/spec/examples/README.md | 27 + .../spec/examples/dart_registry_class.json | 1 + x/registry/types/authorization.go | 53 ++ x/registry/types/class.go | 105 ++- 6 files changed, 857 insertions(+), 1 deletion(-) create mode 100644 x/registry/keeper/dart_role_policies_acceptance_test.go create mode 100644 x/registry/spec/examples/README.md create mode 100644 x/registry/spec/examples/dart_registry_class.json diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go index 5e6b76bf77..684acfc95e 100644 --- a/x/registry/keeper/class.go +++ b/x/registry/keeper/class.go @@ -37,6 +37,10 @@ func (k Keeper) SetRegistryClass(ctx context.Context, class types.RegistryClass) // CreateRegistryClass stores a new registry class. It returns an error if a class already exists // with the same id. func (k Keeper) CreateRegistryClass(ctx context.Context, class types.RegistryClass) error { + if err := class.Validate(); err != nil { + return err + } + has, err := k.RegistryClasses.Has(ctx, class.RegistryClassId) if err != nil { return fmt.Errorf("could not check if registry class exists: %w", err) @@ -72,6 +76,9 @@ func (k Keeper) UpdateRegistryClassRoleAuthorization(ctx context.Context, signer } class.RoleAuthorizations = roleAuths + if err = class.Validate(); err != nil { + return err + } if err = k.SetRegistryClass(ctx, *class); err != nil { return fmt.Errorf("could not set registry class: %w", err) } diff --git a/x/registry/keeper/dart_role_policies_acceptance_test.go b/x/registry/keeper/dart_role_policies_acceptance_test.go new file mode 100644 index 0000000000..c706aa96fa --- /dev/null +++ b/x/registry/keeper/dart_role_policies_acceptance_test.go @@ -0,0 +1,665 @@ +package keeper_test + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/x/nft" + nftkeeper "cosmossdk.io/x/nft/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// DartRolePoliciesAcceptanceTestSuite proves that the DART participant role policies described in +// the ticket (sc-512248, requirements.md §"DART Participant Roles") are expressible as ordinary +// RegistryClass.role_authorizations data and are correctly evaluated by the policy engine — without +// any hard-coded, per-role chain logic. The same policies double as the example fixture shipped in +// x/registry/spec/examples/dart_registry_class.json. +type DartRolePoliciesAcceptanceTestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + registryKeeper keeper.Keeper + nftKeeper nftkeeper.Keeper + msgServer types.MsgServer + + nftClass nft.Class + + maintainer string + nftOwner string +} + +func TestDartRolePoliciesAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(DartRolePoliciesAcceptanceTestSuite)) +} + +const dartClassID = "dart-loan-v1" + +func (s *DartRolePoliciesAcceptanceTestSuite) SetupTest() { + s.app = app.Setup(s.T()) + s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + + s.nftKeeper = s.app.NFTKeeper + s.registryKeeper = s.app.RegistryKeeper + s.msgServer = keeper.NewMsgServer(s.registryKeeper) + + s.maintainer = genAddr() + s.nftOwner = genAddr() + + s.nftClass = nft.Class{Id: "dart-roles-test-nft-class"} + s.nftKeeper.SaveClass(s.ctx, s.nftClass) + + // Install the full DART registry class so every classed entry is governed by these policies. + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: s.maintainer, + RegistryClassId: dartClassID, + AssetClassId: s.nftClass.Id, + Maintainer: s.maintainer, + RoleAuthorizations: dartRoleAuthorizations(), + }) + s.Require().NoError(err) +} + +// registerWithRoles mints an NFT owned by s.nftOwner, registers it under the DART class, and seeds +// the given initial roles. It returns the registry key. +func (s *DartRolePoliciesAcceptanceTestSuite) registerWithRoles(id string, roles []types.RolesEntry) *types.RegistryKey { + n := nft.NFT{ClassId: s.nftClass.Id, Id: id} + ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) + s.Require().NoError(err) + s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) + + key := &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles, dartClassID)) + return key +} + +// propose opens a pending role change and returns the change id and whether it applied immediately. +func (s *DartRolePoliciesAcceptanceTestSuite) propose(key *types.RegistryKey, signer string, role types.RegistryRole, addrs []string) (string, bool) { + resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: signer, + Key: key, + RoleUpdates: []types.RoleUpdate{{Role: role, Addresses: addrs}}, + }) + s.Require().NoError(err) + return resp.ChangeId, resp.Applied +} + +// proposeErr attempts a proposal expected to be rejected because the proposer is not an eligible +// approver of the change, and returns the error. +func (s *DartRolePoliciesAcceptanceTestSuite) proposeErr(key *types.RegistryKey, signer string, role types.RegistryRole, addrs []string) error { + _, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: signer, + Key: key, + RoleUpdates: []types.RoleUpdate{{Role: role, Addresses: addrs}}, + }) + return err +} + +// approve records an approval for a pending change and returns whether it applied. +func (s *DartRolePoliciesAcceptanceTestSuite) approve(changeID, signer string) bool { + resp, err := s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{ + Signer: signer, + ChangeId: changeID, + }) + s.Require().NoError(err) + return resp.Applied +} + +// roleAddresses returns the addresses currently assigned to role on the entry. +func (s *DartRolePoliciesAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { + entry, err := s.registryKeeper.GetRegistry(s.ctx, key) + s.Require().NoError(err) + s.Require().NotNil(entry) + for _, re := range entry.Roles { + if re.Role == role { + return re.Addresses + } + } + return nil +} + +// --- Consent-matrix tests ----------------------------------------------------------------------- + +// TestOriginator: an originator update requires the current originator (or NFT owner if unset) plus +// the incoming new originator. +func (s *DartRolePoliciesAcceptanceTestSuite) TestOriginator() { + role := types.RegistryRole_REGISTRY_ROLE_ORIGINATOR + currentOriginator := genAddr() + newOriginator := genAddr() + + s.Run("current + new originator approve", func() { + key := s.registerWithRoles("orig-happy", []types.RolesEntry{{Role: role, Addresses: []string{currentOriginator}}}) + + changeID, applied := s.propose(key, currentOriginator, role, []string{newOriginator}) + s.Require().False(applied, "current originator alone is not sufficient") + + applied = s.approve(changeID, newOriginator) + s.Require().True(applied, "current + new originator satisfy the policy") + s.Require().Equal([]string{newOriginator}, s.roleAddresses(key, role)) + }) + + s.Run("NFT owner acts as current originator when none is set", func() { + key := s.registerWithRoles("orig-nft-owner", nil) + + changeID, applied := s.propose(key, s.nftOwner, role, []string{newOriginator}) + s.Require().False(applied) + + applied = s.approve(changeID, newOriginator) + s.Require().True(applied) + s.Require().Equal([]string{newOriginator}, s.roleAddresses(key, role)) + }) + + s.Run("stranger cannot originate", func() { + key := s.registerWithRoles("orig-stranger", []types.RolesEntry{{Role: role, Addresses: []string{currentOriginator}}}) + + err := s.proposeErr(key, genAddr(), role, []string{newOriginator}) + s.Require().Error(err, "an ineligible proposer is rejected outright") + s.Require().Equal([]string{currentOriginator}, s.roleAddresses(key, role)) + }) +} + +// TestLienOwnerStandard: a standard lien owner transfer requires the current lien owner, the current +// Secured Party for Lien (if set), and the new lien owner. +func (s *DartRolePoliciesAcceptanceTestSuite) TestLienOwnerStandard() { + lienOwner := types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER + securedParty := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN + + currentLienOwner := genAddr() + sp := genAddr() + newLienOwner := genAddr() + + baseRoles := func() []types.RolesEntry { + return []types.RolesEntry{ + {Role: lienOwner, Addresses: []string{currentLienOwner}}, + {Role: securedParty, Addresses: []string{sp}}, + } + } + + s.Run("current lien owner + secured party + new lien owner", func() { + key := s.registerWithRoles("lien-happy", baseRoles()) + + changeID, applied := s.propose(key, currentLienOwner, lienOwner, []string{newLienOwner}) + s.Require().False(applied) + + applied = s.approve(changeID, sp) + s.Require().False(applied, "still missing the new lien owner") + + applied = s.approve(changeID, newLienOwner) + s.Require().True(applied) + s.Require().Equal([]string{newLienOwner}, s.roleAddresses(key, lienOwner)) + }) + + s.Run("incomplete: missing secured party approval", func() { + key := s.registerWithRoles("lien-no-sp", baseRoles()) + + changeID, _ := s.propose(key, currentLienOwner, lienOwner, []string{newLienOwner}) + applied := s.approve(changeID, newLienOwner) + s.Require().False(applied, "secured party for lien must also approve") + s.Require().Equal([]string{currentLienOwner}, s.roleAddresses(key, lienOwner)) + }) +} + +// TestLienOwnerForeclosure: the Secured Party for Lien can unilaterally become the Lien Owner. A +// single proposal by the secured party (assigning the lien owner role to itself) satisfies the +// dedicated foreclosure authorization path. +func (s *DartRolePoliciesAcceptanceTestSuite) TestLienOwnerForeclosure() { + lienOwner := types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER + securedParty := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN + + currentLienOwner := genAddr() + sp := genAddr() + + s.Run("secured party unilaterally forecloses into lien owner", func() { + key := s.registerWithRoles("lien-foreclose", []types.RolesEntry{ + {Role: lienOwner, Addresses: []string{currentLienOwner}}, + {Role: securedParty, Addresses: []string{sp}}, + }) + + _, applied := s.propose(key, sp, lienOwner, []string{sp}) + s.Require().True(applied, "foreclosure path: current secured party becomes the new lien owner") + s.Require().Equal([]string{sp}, s.roleAddresses(key, lienOwner)) + }) + + s.Run("stranger cannot foreclose", func() { + key := s.registerWithRoles("lien-foreclose-bad", []types.RolesEntry{ + {Role: lienOwner, Addresses: []string{currentLienOwner}}, + {Role: securedParty, Addresses: []string{sp}}, + }) + + stranger := genAddr() + _, applied := s.propose(key, stranger, lienOwner, []string{stranger}) + s.Require().False(applied) + s.Require().Equal([]string{currentLienOwner}, s.roleAddresses(key, lienOwner)) + }) +} + +// TestControllerForeclosure: the Secured Party for eNote can unilaterally become the Controller. +func (s *DartRolePoliciesAcceptanceTestSuite) TestControllerForeclosure() { + controller := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + securedParty := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + currentController := genAddr() + sp := genAddr() + + key := s.registerWithRoles("ctrl-foreclose", []types.RolesEntry{ + {Role: controller, Addresses: []string{currentController}}, + {Role: securedParty, Addresses: []string{sp}}, + }) + + _, applied := s.propose(key, sp, controller, []string{sp}) + s.Require().True(applied, "foreclosure path: current secured party for eNote becomes the controller") + s.Require().Equal([]string{sp}, s.roleAddresses(key, controller)) +} + +// TestServicer exercises a policy whose conditional requirement uses a role_priority selector +// (Secured Party for eNote, falling back to Pledgee). Granting a servicer requires the current +// controller, the conditional approver, and the new servicer. +func (s *DartRolePoliciesAcceptanceTestSuite) TestServicer() { + servicer := types.RegistryRole_REGISTRY_ROLE_SERVICER + controller := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + spEnote := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE + + currentController := genAddr() + sp := genAddr() + newServicer := genAddr() + + s.Run("controller + secured party for eNote + new servicer", func() { + key := s.registerWithRoles("svc-happy", []types.RolesEntry{ + {Role: controller, Addresses: []string{currentController}}, + {Role: spEnote, Addresses: []string{sp}}, + }) + + changeID, applied := s.propose(key, currentController, servicer, []string{newServicer}) + s.Require().False(applied) + + applied = s.approve(changeID, sp) + s.Require().False(applied, "still missing the new servicer") + + applied = s.approve(changeID, newServicer) + s.Require().True(applied) + s.Require().Equal([]string{newServicer}, s.roleAddresses(key, servicer)) + }) + + s.Run("incomplete: missing conditional secured-party approval", func() { + key := s.registerWithRoles("svc-no-sp", []types.RolesEntry{ + {Role: controller, Addresses: []string{currentController}}, + {Role: spEnote, Addresses: []string{sp}}, + }) + + changeID, _ := s.propose(key, currentController, servicer, []string{newServicer}) + applied := s.approve(changeID, newServicer) + s.Require().False(applied) + s.Require().Empty(s.roleAddresses(key, servicer)) + }) +} + +// TestAllDartPoliciesCoexist confirms the full set validates and persists together. +func (s *DartRolePoliciesAcceptanceTestSuite) TestAllDartPoliciesCoexist() { + got, err := s.registryKeeper.GetRegistryClass(s.ctx, dartClassID) + s.Require().NoError(err) + s.Require().NotNil(got) + s.Require().Len(got.RoleAuthorizations, len(dartRoleAuthorizations())) +} + +// TestMalformedPolicyRejected verifies the deepened create-time validation rejects malformed +// authorization paths instead of letting them fail closed only at evaluation time. +func (s *DartRolePoliciesAcceptanceTestSuite) TestMalformedPolicyRejected() { + cases := []struct { + name string + auth types.RoleAuthorization + errPart string + }{ + { + name: "nft_role with NEW assignment", + auth: types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "bad", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raNft(types.NftRole_NFT_ROLE_NFT_OWNER, types.Assignment_ASSIGNMENT_NEW)), + }, + }}, + }, + errPart: "nft_role may only be used with a CURRENT*", + }, + { + name: "missing role selector", + auth: types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "bad", + Signatures: []*types.SignatureRequirement{ + {Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, Roles: []*types.RoleAssignment{ + {Assignment: types.Assignment_ASSIGNMENT_CURRENT}, + }}, + }, + }}, + }, + errPart: "a role selector", + }, + { + name: "unspecified signature type", + auth: types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "bad", + Signatures: []*types.SignatureRequirement{ + {Roles: []*types.RoleAssignment{raRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER, types.Assignment_ASSIGNMENT_CURRENT)}}, + }, + }}, + }, + errPart: "type", + }, + { + name: "authorization path with no signature requirements", + auth: types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{Description: "empty"}}, + }, + errPart: "at least one signature requirement", + }, + } + + for _, tc := range cases { + s.Run(tc.name, func() { + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: s.maintainer, + RegistryClassId: "bad-" + tc.name, + AssetClassId: s.nftClass.Id, + Maintainer: s.maintainer, + RoleAuthorizations: []types.RoleAuthorization{tc.auth}, + }) + s.Require().Error(err) + s.Require().ErrorContains(err, tc.errPart) + }) + } +} + +// TestExampleFixtureInSync verifies the committed example fixture proto-JSON decodes to exactly the +// DART policies built in code. Set REGEN_DART_FIXTURE=1 to (re)generate the fixture file. +func (s *DartRolePoliciesAcceptanceTestSuite) TestExampleFixtureInSync() { + path := filepath.Join("..", "spec", "examples", "dart_registry_class.json") + + want := types.RegistryClass{ + RegistryClassId: dartClassID, + AssetClassId: "loan.asset", + Maintainer: "pb1maintainerplaceholder0000000000000000000", + RoleAuthorizations: dartRoleAuthorizations(), + } + + if os.Getenv("REGEN_DART_FIXTURE") == "1" { + bz, err := s.app.AppCodec().MarshalJSON(&want) + s.Require().NoError(err) + s.Require().NoError(os.WriteFile(path, append(bz, '\n'), 0o644)) + } + + bz, err := os.ReadFile(path) + s.Require().NoError(err, "example fixture missing; run with REGEN_DART_FIXTURE=1 to generate") + + var got types.RegistryClass + s.Require().NoError(s.app.AppCodec().UnmarshalJSON(bz, &got)) + s.Require().Equal(want.RoleAuthorizations, got.RoleAuthorizations, + "example fixture is out of sync with code; run with REGEN_DART_FIXTURE=1 to regenerate") +} + +// --- DART policy builders (mirror requirements.md §"DART Participant Roles") --------------------- + +// dartRoleAuthorizations returns the full set of DART participant role policies. +func dartRoleAuthorizations() []types.RoleAuthorization { + return []types.RoleAuthorization{ + originatorPolicy(), + lienOwnerPolicy(), + controllerPolicy(), + securedPartyForLienPolicy(), + securedPartyForEnotePolicy(), + pledgeePolicy(), + servicerPolicy(), + subservicerPolicy(), + } +} + +func originatorPolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_ORIGINATOR, + Authorizations: []*types.Authorization{{ + Description: "Assign originator (requires current and new originator approval)", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_ORIGINATOR), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet(raRegistry(types.RegistryRole_REGISTRY_ROLE_ORIGINATOR, types.Assignment_ASSIGNMENT_NEW)), + }, + }}, + } +} + +func lienOwnerPolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER, + Authorizations: []*types.Authorization{ + { + Description: "Transfer requiring current lien owner approval", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet( + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }, + { + Description: "Foreclosure: Secured Party for Lien can unilaterally become Lien Owner in case of default", + Signatures: []*types.SignatureRequirement{ + sigReqAll( + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }, + }, + } +} + +func controllerPolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Authorizations: []*types.Authorization{ + { + Description: "Transfer requiring current controller approval", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet( + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }, + { + Description: "Foreclosure: Secured Party for eNote can unilaterally become Controller in case of default", + Signatures: []*types.SignatureRequirement{ + sigReqAll( + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }, + }, + } +} + +func securedPartyForLienPolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, + Authorizations: []*types.Authorization{ + { + Description: "Update by current lien owner", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet( + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }, + { + Description: "Update by current secured party for lien", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, types.Assignment_ASSIGNMENT_CURRENT)), + sigReqAllIfSet(raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN, types.Assignment_ASSIGNMENT_NEW)), + }, + }, + }, + } +} + +func securedPartyForEnotePolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, + Authorizations: []*types.Authorization{ + { + Description: "Update by current controller", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet( + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }, + { + Description: "Update by current secured party for eNote", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, types.Assignment_ASSIGNMENT_CURRENT)), + sigReqAllIfSet(raRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, types.Assignment_ASSIGNMENT_NEW)), + }, + }, + }, + } +} + +func pledgeePolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_PLEDGEE, + Authorizations: []*types.Authorization{{ + Description: "Update pledgee with value owner approval", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raNft(types.NftRole_NFT_ROLE_NFT_OWNER, types.Assignment_ASSIGNMENT_CURRENT)), + sigReqAllIfSet( + raRegistry(types.RegistryRole_REGISTRY_ROLE_PLEDGEE, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_PLEDGEE, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }}, + } +} + +func servicerPolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "Update servicer with owner/controller approval", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet( + raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE), + rpRegistry(types.RegistryRole_REGISTRY_ROLE_PLEDGEE), + ), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SERVICER, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SERVICER, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }}, + } +} + +func subservicerPolicy() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SUBSERVICER, + Authorizations: []*types.Authorization{{ + Description: "Update sub-servicer with owner/controller and servicer approval", + Signatures: []*types.SignatureRequirement{ + sigReqAll(raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_CONTROLLER), + rpNft(types.NftRole_NFT_ROLE_NFT_OWNER), + )), + sigReqAllIfSet( + raPriority(types.Assignment_ASSIGNMENT_CURRENT, + rpRegistry(types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE), + rpRegistry(types.RegistryRole_REGISTRY_ROLE_PLEDGEE), + ), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SERVICER, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SUBSERVICER, types.Assignment_ASSIGNMENT_CURRENT), + raRegistry(types.RegistryRole_REGISTRY_ROLE_SUBSERVICER, types.Assignment_ASSIGNMENT_NEW), + ), + }, + }}, + } +} + +// --- terse builder helpers ---------------------------------------------------------------------- + +func sigReqAll(roles ...*types.RoleAssignment) *types.SignatureRequirement { + return &types.SignatureRequirement{Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, Roles: roles} +} + +func sigReqAllIfSet(roles ...*types.RoleAssignment) *types.SignatureRequirement { + return &types.SignatureRequirement{Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET, Roles: roles} +} + +func raRegistry(role types.RegistryRole, assignment types.Assignment) *types.RoleAssignment { + return &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: role}, + Assignment: assignment, + } +} + +func raNft(nftRole types.NftRole, assignment types.Assignment) *types.RoleAssignment { + return &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_NftRole{NftRole: nftRole}, + Assignment: assignment, + } +} + +func raPriority(assignment types.Assignment, entries ...*types.RolePriorityEntry) *types.RoleAssignment { + return &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: &types.RolePriority{Entries: entries}}, + Assignment: assignment, + } +} + +func rpRegistry(role types.RegistryRole) *types.RolePriorityEntry { + return &types.RolePriorityEntry{Role: &types.RolePriorityEntry_RegistryRole{RegistryRole: role}} +} + +func rpNft(nftRole types.NftRole) *types.RolePriorityEntry { + return &types.RolePriorityEntry{Role: &types.RolePriorityEntry_NftRole{NftRole: nftRole}} +} diff --git a/x/registry/spec/examples/README.md b/x/registry/spec/examples/README.md new file mode 100644 index 0000000000..49300d66b0 --- /dev/null +++ b/x/registry/spec/examples/README.md @@ -0,0 +1,27 @@ +# Registry class examples + +This directory holds example registry classes that can be created with the registry CLI. + +## `dart_registry_class.json` + +A complete DART (Digital Asset Registry Technology) registry class expressing the participant role +policies from the ticket (sc-512248) as ordinary `role_authorizations` data: Originator, Lien Owner +(with foreclosure), Controller (with foreclosure), Secured Party for Lien, Secured Party for eNote, +Pledgee, Servicer, and Sub-servicer. + +These policies are evaluated by the registry's authorization engine; none of them are hard-coded into +the chain. A maintainer installs them per asset class: + +```bash +provenanced tx registry create-registry-class dart_registry_class.json --from +``` + +Before submitting, set `registry_class_id`, `asset_class_id`, and `maintainer` to your values (the +`maintainer` must equal the `--from` signer, or it can be omitted to default to that signer). + +The file is kept in sync with the policy builders in +`x/registry/keeper/dart_role_policies_acceptance_test.go`; regenerate it with: + +```bash +REGEN_DART_FIXTURE=1 go test ./x/registry/keeper/ -run TestDartRolePoliciesAcceptanceTestSuite/TestExampleFixtureInSync +``` diff --git a/x/registry/spec/examples/dart_registry_class.json b/x/registry/spec/examples/dart_registry_class.json new file mode 100644 index 0000000000..3c35111fa7 --- /dev/null +++ b/x/registry/spec/examples/dart_registry_class.json @@ -0,0 +1 @@ +{"registry_class_id":"dart-loan-v1","asset_class_id":"loan.asset","maintainer":"pb1maintainerplaceholder0000000000000000000","role_authorizations":[{"role":"REGISTRY_ROLE_ORIGINATOR","authorizations":[{"description":"Assign originator (requires current and new originator approval)","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_ORIGINATOR"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_ORIGINATOR","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_LIEN_OWNER","authorizations":[{"description":"Transfer requiring current lien owner approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_LIEN_OWNER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_LIEN_OWNER","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Foreclosure: Secured Party for Lien can unilaterally become Lien Owner in case of default","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_LIEN_OWNER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_CONTROLLER","authorizations":[{"description":"Transfer requiring current controller approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_CONTROLLER","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Foreclosure: Secured Party for eNote can unilaterally become Controller in case of default","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_CONTROLLER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","authorizations":[{"description":"Update by current lien owner","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_LIEN_OWNER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Update by current secured party for lien","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","authorizations":[{"description":"Update by current controller","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Update by current secured party for eNote","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_PLEDGEE","authorizations":[{"description":"Update pledgee with value owner approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"nft_role":"NFT_ROLE_NFT_OWNER","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_PLEDGEE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_PLEDGEE","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SERVICER","authorizations":[{"description":"Update servicer with owner/controller approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE"},{"registry_role":"REGISTRY_ROLE_PLEDGEE"}]},"assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SUBSERVICER","authorizations":[{"description":"Update sub-servicer with owner/controller and servicer approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE"},{"registry_role":"REGISTRY_ROLE_PLEDGEE"}]},"assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SUBSERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SUBSERVICER","assignment":"ASSIGNMENT_NEW"}]}]}]}]} diff --git a/x/registry/types/authorization.go b/x/registry/types/authorization.go index c59a902984..eb26966d8a 100644 --- a/x/registry/types/authorization.go +++ b/x/registry/types/authorization.go @@ -1,5 +1,58 @@ package types +import ( + "fmt" + + "github.com/provenance-io/provenance/internal/provutils" +) + +// Validate returns an error if this SignatureType is not a defined enum entry, or is unspecified. +func (t SignatureType) Validate() error { + if err := provutils.EnumValidateExists(t, SignatureType_name); err != nil { + return err + } + if t == SignatureType_SIGNATURE_TYPE_UNSPECIFIED { + return fmt.Errorf("cannot be unspecified") + } + return nil +} + +// Validate returns an error if this NftRole is not a defined enum entry, or is unspecified. +func (r NftRole) Validate() error { + if err := provutils.EnumValidateExists(r, NftRole_name); err != nil { + return err + } + if r == NftRole_NFT_ROLE_UNSPECIFIED { + return fmt.Errorf("cannot be unspecified") + } + return nil +} + +// Validate returns an error if this Assignment is not a defined enum entry, or is unspecified. +func (a Assignment) Validate() error { + if err := provutils.EnumValidateExists(a, Assignment_name); err != nil { + return err + } + if a == Assignment_ASSIGNMENT_UNSPECIFIED { + return fmt.Errorf("cannot be unspecified") + } + return nil +} + +// IsCurrent reports whether this assignment resolves currently-held addresses (the CURRENT* family) +// rather than incoming new addresses (the NEW* family). NftRole selectors are only valid with the +// CURRENT* family because the registry module cannot assign NFT-module roles. +func (a Assignment) IsCurrent() bool { + switch a { + case Assignment_ASSIGNMENT_CURRENT, + Assignment_ASSIGNMENT_CURRENT_ALL, + Assignment_ASSIGNMENT_CURRENT_ANY: + return true + default: + return false + } +} + // ControllerRoleAuthorizations returns the example CONTROLLER authorization policy from the ticket. // This is NOT the default chain behavior: by default the registry module has no role policies and // every role change falls back to legacy NFT-owner authorization. This policy set is provided as a diff --git a/x/registry/types/class.go b/x/registry/types/class.go index 26ef558eed..8b6dbdf179 100644 --- a/x/registry/types/class.go +++ b/x/registry/types/class.go @@ -46,7 +46,8 @@ func ValidateRegistryClassID(registryClassID string) error { } // validateRoleAuthorizations validates a set of role authorization policies. Each policy must -// reference a known, specified role, and a role may be configured at most once. +// reference a known, specified role, a role may be configured at most once, and every authorization +// path's signature requirements and role selectors must be well-formed. func validateRoleAuthorizations(auths []RoleAuthorization) []error { var errs []error seen := make(map[RegistryRole]bool, len(auths)) @@ -62,6 +63,108 @@ func validateRoleAuthorizations(auths []RoleAuthorization) []error { if len(auth.Authorizations) == 0 { errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: at least one authorization path is required", i)) } + for j, path := range auth.Authorizations { + errs = append(errs, validateAuthorizationPath(i, j, path)...) + } + } + return errs +} + +// validateAuthorizationPath validates a single authorization path (one of the "any path satisfies" +// alternatives) within a role authorization policy. i/j are the policy/path indices for error +// context. +func validateAuthorizationPath(i, j int, path *Authorization) []error { + var errs []error + if path == nil { + return []error{NewErrCodeInvalidField("role_authorizations", "%d: authorization %d is nil", i, j)} + } + if len(path.Signatures) == 0 { + errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: authorization %d: at least one signature requirement is required", i, j)) + } + for k, req := range path.Signatures { + errs = append(errs, validateSignatureRequirement(i, j, k, req)...) + } + return errs +} + +// validateSignatureRequirement validates a single signature requirement: a known signature type and +// at least one well-formed role selector. i/j/k are the policy/path/requirement indices. +func validateSignatureRequirement(i, j, k int, req *SignatureRequirement) []error { + var errs []error + if req == nil { + return []error{NewErrCodeInvalidField("role_authorizations", "%d: authorization %d: signature %d is nil", i, j, k)} + } + if err := req.Type.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: authorization %d: signature %d: type %s", i, j, k, err)) + } + if len(req.Roles) == 0 { + errs = append(errs, NewErrCodeInvalidField("role_authorizations", "%d: authorization %d: signature %d: at least one role selector is required", i, j, k)) + } + for l, ra := range req.Roles { + errs = append(errs, validateRoleAssignment(i, j, k, l, ra)...) + } + return errs +} + +// validateRoleAssignment validates a single role selector within a signature requirement. It +// enforces that exactly one selector kind is set, that referenced enum values are defined, and that +// NftRole selectors only use the CURRENT* assignment family. i/j/k/l are the nested indices. +func validateRoleAssignment(i, j, k, l int, ra *RoleAssignment) []error { + prefix := func(format string, args ...interface{}) error { + return NewErrCodeInvalidField("role_authorizations", + "%d: authorization %d: signature %d: role %d: "+format, append([]interface{}{i, j, k, l}, args...)...) + } + if ra == nil { + return []error{prefix("role selector is nil")} + } + + var errs []error + if err := ra.Assignment.Validate(); err != nil { + errs = append(errs, prefix("assignment %s", err)) + } + + switch r := ra.RoleSelector.(type) { + case *RoleAssignment_RegistryRole: + if err := r.RegistryRole.Validate(); err != nil { + errs = append(errs, prefix("registry_role %s", err)) + } + case *RoleAssignment_NftRole: + if err := r.NftRole.Validate(); err != nil { + errs = append(errs, prefix("nft_role %s", err)) + } else if !ra.Assignment.IsCurrent() { + errs = append(errs, prefix("nft_role may only be used with a CURRENT* assignment, got %s", ra.Assignment.String())) + } + case *RoleAssignment_RolePriority: + errs = append(errs, validateRolePriority(prefix, ra.Assignment, r.RolePriority)...) + default: + errs = append(errs, prefix("a role selector (registry_role, nft_role, or role_priority) must be set")) + } + return errs +} + +// validateRolePriority validates a role_priority fallback chain: it must contain at least one entry, +// and every entry must set exactly one selector with a defined enum value. NftRole entries inherit +// the assignment's CURRENT* constraint. +func validateRolePriority(prefix func(string, ...interface{}) error, assignment Assignment, rp *RolePriority) []error { + if rp == nil || len(rp.Entries) == 0 { + return []error{prefix("role_priority must contain at least one entry")} + } + var errs []error + for m, e := range rp.Entries { + switch r := e.Role.(type) { + case *RolePriorityEntry_RegistryRole: + if err := r.RegistryRole.Validate(); err != nil { + errs = append(errs, prefix("role_priority entry %d: registry_role %s", m, err)) + } + case *RolePriorityEntry_NftRole: + if err := r.NftRole.Validate(); err != nil { + errs = append(errs, prefix("role_priority entry %d: nft_role %s", m, err)) + } else if !assignment.IsCurrent() { + errs = append(errs, prefix("role_priority entry %d: nft_role may only be used with a CURRENT* assignment, got %s", m, assignment.String())) + } + default: + errs = append(errs, prefix("role_priority entry %d: a role (registry_role or nft_role) must be set", m)) + } } return errs } From 0fa21fb80e1eaf5eac4ffb630e79279645c24d6f Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 09:31:27 -0600 Subject: [PATCH 18/36] registry: add EventRoleUpdated event and ValidateRoleChange query (C4, C5) Phase C4: emit a comprehensive provenance.registry.v1.EventRoleUpdated (nft_id, asset_class_id, registry_class_id, role, addresses, previous_addresses, signers) additively alongside the existing granular EventRoleGranted/EventRoleRevoked events. The satisfying authorization path's role/assignment/addresses are surfaced via CollectSatisfyingSigners; the legacy non-policy path reports the NFT owner at ASSIGNMENT_CURRENT. Emitted from GrantRole/RevokeRole/SetRoles and the pending-apply path (per-role, with pre-mutation previous_addresses). Phase C5: add a ValidateRoleChange dry-run query (QueryValidateRoleChangeRequest{key, role_updates, approvers} -> {error, authorized}) that reuses the accumulation evaluation read-only via EvaluateRoleChange with no state writes. Adds the rpc/messages to query.proto, a query_server handler, and the GetCmdQueryValidateRoleChange CLI command. Bad request shapes return gRPC InvalidArgument. Includes role_event_query_acceptance_test.go covering both phases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/events.proto | 33 + proto/provenance/registry/v1/query.proto | 31 + x/registry/client/cli/query.go | 52 + x/registry/keeper/authorization.go | 106 ++ x/registry/keeper/keeper.go | 29 + x/registry/keeper/msg_server.go | 11 + x/registry/keeper/pending.go | 59 +- x/registry/keeper/query_server.go | 24 + .../role_event_query_acceptance_test.go | 312 ++++++ x/registry/types/events.go | 24 + x/registry/types/events.pb.go | 936 ++++++++++++++++-- x/registry/types/query.pb.go | 693 +++++++++++-- x/registry/types/query.pb.gw.go | 141 +++ 13 files changed, 2312 insertions(+), 139 deletions(-) create mode 100644 x/registry/keeper/role_event_query_acceptance_test.go diff --git a/proto/provenance/registry/v1/events.proto b/proto/provenance/registry/v1/events.proto index 028ac5a127..2b3bfbc556 100644 --- a/proto/provenance/registry/v1/events.proto +++ b/proto/provenance/registry/v1/events.proto @@ -5,6 +5,8 @@ option go_package = "github.com/provenance-io/provenance/x/registry/typ option java_package = "io.provenance.registry.v1"; option java_multiple_files = true; +import "gogoproto/gogo.proto"; + // EventNFTRegistered is the event emitted when a registry is created for an NFT. message EventNFTRegistered { string nft_id = 1; @@ -75,3 +77,34 @@ message EventRegistryClassUpdated { // EventParamsUpdated is emitted when the registry module's params are updated via governance. message EventParamsUpdated {} + +// RoleSigner describes a role/assignment whose resolved addresses contributed a signature toward +// authorizing a role change. It is the structured form of an entry in EventRoleUpdated.signers. +message RoleSigner { + // role is the (registry or NFT) role whose holders provided the signature(s). + string role = 1; + // assignment is the assignment variant the role was resolved under (e.g. ASSIGNMENT_CURRENT). + string assignment = 2; + // addresses are the resolved addresses for the role that signed to authorize the change. + repeated string addresses = 3; +} + +// EventRoleUpdated is the comprehensive event emitted whenever a role is granted, revoked, or set +// for a registered NFT. It is emitted for MsgGrantRole, MsgRevokeRole, and once per updated role +// for MsgSetRoles (including changes applied via the propose/approve accumulation flow). +message EventRoleUpdated { + // nft_id is the ID of the NFT for which the role was updated. + string nft_id = 1; + // asset_class_id is the ID of the asset class the NFT belongs to. + string asset_class_id = 2; + // registry_class_id is the registry class governing this NFT (empty if none). + string registry_class_id = 3; + // role is the role that was updated. + string role = 4; + // addresses are the addresses assigned to the role after this operation (empty if cleared). + repeated string addresses = 5; + // previous_addresses are the addresses assigned to the role before this operation. + repeated string previous_addresses = 6; + // signers describes the roles/addresses that authorized this change. + repeated RoleSigner signers = 7 [(gogoproto.nullable) = false]; +} diff --git a/proto/provenance/registry/v1/query.proto b/proto/provenance/registry/v1/query.proto index bd76398f70..100e4b3c24 100644 --- a/proto/provenance/registry/v1/query.proto +++ b/proto/provenance/registry/v1/query.proto @@ -58,6 +58,14 @@ service Query { rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { option (google.api.http).get = "/provenance/registry/v1/params"; } + + // ValidateRoleChange performs a read-only dry-run of a role-change batch, reporting whether the + // supplied approvers would satisfy every affected role's authorization policy without writing + // any state. + rpc ValidateRoleChange(QueryValidateRoleChangeRequest) returns (QueryValidateRoleChangeResponse) { + option (google.api.http).get = + "/provenance/registry/v1/validate_role_change/{key.asset_class_id}/{key.nft_id}"; + } } // QueryGetRegistryRequest is the request type for the Query/GetRegistry RPC method. @@ -189,3 +197,26 @@ message QueryParamsResponse { // params are the current registry module parameters. Params params = 1 [(gogoproto.nullable) = false]; } + +// QueryValidateRoleChangeRequest is the request type for the Query/ValidateRoleChange RPC method. +// It describes a candidate role-change batch and the set of approvers to test against each affected +// role's authorization policy. +message QueryValidateRoleChangeRequest { + // key identifies the registry entry the role change targets. + RegistryKey key = 1; + + // role_updates is the desired-state batch to evaluate (same shape as MsgSetRoles). + repeated RoleUpdate role_updates = 2 [(gogoproto.nullable) = false]; + + // approvers is the accumulated/candidate set of approver addresses to test. + repeated string approvers = 3; +} + +// QueryValidateRoleChangeResponse is the response type for the Query/ValidateRoleChange RPC method. +message QueryValidateRoleChangeResponse { + // error contains the authorization failure explanation. It is empty when the change is authorized. + string error = 1; + + // authorized is true if the supplied approvers satisfy every affected role's policy. + bool authorized = 2; +} diff --git a/x/registry/client/cli/query.go b/x/registry/client/cli/query.go index 8ec09859e5..b8c18195e4 100644 --- a/x/registry/client/cli/query.go +++ b/x/registry/client/cli/query.go @@ -2,6 +2,7 @@ package cli import ( "context" + "encoding/json" "fmt" "github.com/spf13/cobra" @@ -31,6 +32,7 @@ func CmdQuery() *cobra.Command { GetCmdQueryRegistryClass(), GetCmdQueryRegistryClasses(), GetCmdQueryParams(), + GetCmdQueryValidateRoleChange(), ) return cmd @@ -312,3 +314,53 @@ func GetCmdQueryParams() *cobra.Command { flags.AddQueryFlagsToCmd(cmd) return cmd } + +// GetCmdQueryValidateRoleChange returns the command for the ValidateRoleChange dry-run query. +func GetCmdQueryValidateRoleChange() *cobra.Command { + cmd := &cobra.Command{ + Use: "validate-role-change ", + Short: "Dry-run a role-change batch against a set of approvers", + Long: `Check whether a set of approvers would satisfy every affected role's policy, without +writing any state. role_updates_json is a JSON array of RoleUpdate, e.g.: + + '[{"role":"REGISTRY_ROLE_CONTROLLER","addresses":["pb1newcontroller..."]}]' + +Pass one or more --approver flags with the candidate approver addresses.`, + Args: cobra.ExactArgs(3), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + var roleUpdates []types.RoleUpdate + if err := json.Unmarshal([]byte(args[2]), &roleUpdates); err != nil { + return fmt.Errorf("failed to parse role_updates_json: %w", err) + } + + approvers, err := cmd.Flags().GetStringArray("approver") + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + res, err := queryClient.ValidateRoleChange(context.Background(), &types.QueryValidateRoleChangeRequest{ + Key: &types.RegistryKey{ + AssetClassId: args[0], + NftId: args[1], + }, + RoleUpdates: roleUpdates, + Approvers: approvers, + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + cmd.Flags().StringArray("approver", nil, "a candidate approver address (repeatable)") + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/registry/keeper/authorization.go b/x/registry/keeper/authorization.go index 59b1f3ee12..a2b7e3ca25 100644 --- a/x/registry/keeper/authorization.go +++ b/x/registry/keeper/authorization.go @@ -299,3 +299,109 @@ func (k Keeper) CollectPolicyApprovers(ctx context.Context, roleAuth types.RoleA } return out } + +// resolveRoleAssignmentSigner is like resolveRoleAssignmentAddresses but also returns the concrete +// role name that resolved (used to build EventRoleUpdated.signers). For a role_priority selector it +// returns the first matching priority entry's role name. +func (k Keeper) resolveRoleAssignmentSigner(ctx context.Context, entry *types.RegistryEntry, ra *types.RoleAssignment, newAddrs []string) (string, []string, bool, error) { + if ra == nil { + return "", nil, false, nil + } + switch r := ra.RoleSelector.(type) { + case *types.RoleAssignment_RegistryRole: + addrs, exists, err := k.resolveRegistryRoleAddresses(entry, r.RegistryRole, ra.Assignment, newAddrs) + return r.RegistryRole.String(), addrs, exists, err + case *types.RoleAssignment_NftRole: + addrs, exists, err := k.resolveNFTRoleAddresses(ctx, entry, r.NftRole, ra.Assignment) + return r.NftRole.String(), addrs, exists, err + case *types.RoleAssignment_RolePriority: + if r.RolePriority == nil { + return "", nil, false, nil + } + for _, e := range r.RolePriority.Entries { + switch pe := e.Role.(type) { + case *types.RolePriorityEntry_RegistryRole: + addrs, exists, err := k.resolveRegistryRoleAddresses(entry, pe.RegistryRole, ra.Assignment, newAddrs) + if err != nil { + return "", nil, false, err + } + if exists { + return pe.RegistryRole.String(), addrs, true, nil + } + case *types.RolePriorityEntry_NftRole: + addrs, exists, err := k.resolveNFTRoleAddresses(ctx, entry, pe.NftRole, ra.Assignment) + if err != nil { + return "", nil, false, err + } + if exists { + return pe.NftRole.String(), addrs, true, nil + } + } + } + return "", nil, false, nil + default: + return "", nil, false, nil + } +} + +// collectAuthorizationSigners builds the list of RoleSigner entries describing which roles and +// addresses contributed signatures to satisfy the given (already-satisfied) authorization path. +func (k Keeper) collectAuthorizationSigners(ctx context.Context, entry *types.RegistryEntry, auth *types.Authorization, newAddrs []string, signerSet map[string]bool) []types.RoleSigner { + var signers []types.RoleSigner + if auth == nil { + return signers + } + for _, req := range auth.Signatures { + if req == nil { + continue + } + switch req.Type { + case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL_IF_SET: + // In a satisfied path every resolved (set) role signed, so report all of them. + for _, ra := range req.Roles { + name, addrs, exists, err := k.resolveRoleAssignmentSigner(ctx, entry, ra, newAddrs) + if err != nil || !exists || len(addrs) == 0 { + continue + } + signers = append(signers, types.RoleSigner{Role: name, Assignment: ra.Assignment.String(), Addresses: addrs}) + } + case types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY, + types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY_IF_SET: + // Only the role whose address actually signed contributed; report the signing subset. + for _, ra := range req.Roles { + name, addrs, exists, err := k.resolveRoleAssignmentSigner(ctx, entry, ra, newAddrs) + if err != nil || !exists { + continue + } + var signed []string + for _, a := range addrs { + if signerSet[a] { + signed = append(signed, a) + } + } + if len(signed) > 0 { + signers = append(signers, types.RoleSigner{Role: name, Assignment: ra.Assignment.String(), Addresses: signed}) + break + } + } + } + } + return signers +} + +// CollectSatisfyingSigners finds the first authorization path satisfied by signers and returns the +// RoleSigner entries describing who authorized the change. It returns nil if no path is satisfied +// (e.g. for the legacy NFT-owner fallback, which has no policy). +func (k Keeper) CollectSatisfyingSigners(ctx context.Context, roleAuth types.RoleAuthorization, entry *types.RegistryEntry, newAddrs []string, signers []string) []types.RoleSigner { + signerSet := make(map[string]bool, len(signers)) + for _, s := range signers { + signerSet[s] = true + } + for _, auth := range roleAuth.Authorizations { + if k.evaluateAuthorization(ctx, entry, auth, newAddrs, signerSet) == nil { + return k.collectAuthorizationSigners(ctx, entry, auth, newAddrs, signerSet) + } + } + return nil +} diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index e2a22be342..b91ef6939d 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -269,6 +269,35 @@ func (k Keeper) RevokeRole(ctx context.Context, key *types.RegistryKey, role typ return nil } +// roleChangeSigners resolves the RoleSigner entries describing who authorized a role change for the +// given role. For a policy-governed role it returns the satisfying authorization path's signers; for +// a non-policy role it returns the NFT owner among the approvers (the legacy fallback). before is the +// pre-mutation entry, newAddrs the addresses being assigned to the role, and approvers the signers. +func (k Keeper) roleChangeSigners(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, newAddrs, approvers []string) []types.RoleSigner { + roleAuths := k.roleAuthorizationsForEntry(ctx, before) + if roleAuth, ok := roleAuths[role]; ok { + return k.CollectSatisfyingSigners(ctx, roleAuth, before, newAddrs, approvers) + } + for _, a := range approvers { + if k.ValidateNFTOwner(ctx, &before.Key.AssetClassId, &before.Key.NftId, a) == nil { + return []types.RoleSigner{types.NewNFTOwnerSigner(a)} + } + } + return nil +} + +// emitRoleUpdated emits the comprehensive EventRoleUpdated for a single role, reading the +// post-mutation addresses from state. before is the pre-mutation entry (source of previous +// addresses and registry class id), and signers is the authorizing signer set. +func (k Keeper) emitRoleUpdated(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, signers []types.RoleSigner) { + previous := before.GetRoleAddrs(role) + var current []string + if after, err := k.GetRegistry(ctx, before.Key); err == nil && after != nil { + current = after.GetRoleAddrs(role) + } + k.EmitEvent(ctx, types.NewEventRoleUpdated(before.Key, before.RegistryClassId, role, previous, current, signers)) +} + func (k Keeper) HasRole(ctx context.Context, key *types.RegistryKey, role types.RegistryRole, address string) (bool, error) { registryEntry, err := k.Registry.Get(ctx, key.CollKey()) if err != nil { diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index c4f509df0a..55eb5e5792 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -86,6 +86,9 @@ func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*typ return nil, err } + signers := k.roleChangeSigners(ctx, entry, msg.Role, msg.Addresses, []string{msg.Signer}) + k.emitRoleUpdated(ctx, entry, msg.Role, signers) + return &types.MsgGrantRoleResponse{}, nil } @@ -121,6 +124,9 @@ func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*t return nil, err } + signers := k.roleChangeSigners(ctx, entry, msg.Role, nil, []string{msg.Signer}) + k.emitRoleUpdated(ctx, entry, msg.Role, signers) + return &types.MsgRevokeRoleResponse{}, nil } @@ -157,6 +163,11 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types return nil, err } + for _, update := range msg.RoleUpdates { + signers := k.roleChangeSigners(ctx, entry, update.Role, update.Addresses, []string{msg.Signer}) + k.emitRoleUpdated(ctx, entry, update.Role, signers) + } + return &types.MsgSetRolesResponse{}, nil } diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index b366432f50..4a2599d5fd 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -61,6 +61,47 @@ func (k Keeper) GetPendingRoleChanges(ctx context.Context, pagination *query.Pag return changes, pageRes, nil } +// EvaluateRoleChange performs a read-only authorization check of a desired-state role-change batch +// against the supplied approvers, returning the first affected role's authorization error (or nil if +// every affected role's policy is satisfied). It writes no state and is the basis for the +// ValidateRoleChange dry-run query. +func (k Keeper) EvaluateRoleChange(ctx context.Context, key *types.RegistryKey, roleUpdates []types.RoleUpdate, approvers []string) error { + entry, err := k.GetRegistry(ctx, key) + if err != nil { + return err + } + if entry == nil { + return types.NewErrCodeRegistryNotFound(key.String()) + } + + roleAuths := k.roleAuthorizationsForEntry(ctx, entry) + for _, update := range roleUpdates { + roleAuth, ok := roleAuths[update.Role] + if !ok { + // Legacy fallback: at least one approver must own the NFT. + owns := false + for _, a := range approvers { + if k.ValidateNFTOwner(ctx, &key.AssetClassId, &key.NftId, a) == nil { + owns = true + break + } + } + if !owns { + return types.NewErrCodeUnauthorized( + "role " + update.Role.ShortString() + " has no authorization policy; the NFT owner must approve", + ) + } + continue + } + + newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) + if err := k.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, newAddrs, approvers); err != nil { + return err + } + } + return nil +} + // ProposeRoleChange opens (or re-uses) a pending role change for a batch of desired-state role // updates and records the proposer's approval. If the accumulated approvals already satisfy every // affected role's policy, the change is applied immediately. Returns the change id and whether it @@ -152,7 +193,7 @@ func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.Re return false, nil } - if err := k.applyPendingChange(ctx, change); err != nil { + if err := k.applyPendingChange(ctx, entry, change); err != nil { return false, err } if err := k.RemovePendingRoleChange(ctx, change.Id); err != nil { @@ -237,7 +278,17 @@ func additions(current, desired []string) []string { return added } -// applyPendingChange applies the batch of role updates as a single atomic desired-state set. -func (k Keeper) applyPendingChange(ctx context.Context, change *types.PendingRoleChange) error { - return k.SetRoles(ctx, change.Key, change.RoleUpdates) +// applyPendingChange applies the batch of role updates as a single atomic desired-state set and +// emits an EventRoleUpdated per role using the accumulated approvals as the authorizing signer set. +// before is the pre-mutation entry, captured for previous addresses and signer resolution. +func (k Keeper) applyPendingChange(ctx context.Context, before *types.RegistryEntry, change *types.PendingRoleChange) error { + if err := k.SetRoles(ctx, change.Key, change.RoleUpdates); err != nil { + return err + } + for _, update := range change.RoleUpdates { + newAddrs := additions(before.GetRoleAddrs(update.Role), update.Addresses) + signers := k.roleChangeSigners(ctx, before, update.Role, newAddrs, change.Approvals) + k.emitRoleUpdated(ctx, before, update.Role, signers) + } + return nil } diff --git a/x/registry/keeper/query_server.go b/x/registry/keeper/query_server.go index 58c7369b7f..fb21a90ec8 100644 --- a/x/registry/keeper/query_server.go +++ b/x/registry/keeper/query_server.go @@ -184,3 +184,27 @@ func (qs QueryServer) Params(ctx context.Context, req *types.QueryParamsRequest) return &types.QueryParamsResponse{Params: qs.keeper.GetParams(sdkCtx)}, nil } + +// ValidateRoleChange performs a read-only dry-run of a role-change batch, reporting whether the +// supplied approvers would satisfy every affected role's authorization policy. It writes no state. +func (qs QueryServer) ValidateRoleChange(ctx context.Context, req *types.QueryValidateRoleChangeRequest) (*types.QueryValidateRoleChangeResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "empty request") + } + if req.Key == nil { + return nil, status.Error(codes.InvalidArgument, "key is required") + } + if err := req.Key.Validate(); err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + if len(req.RoleUpdates) == 0 { + return nil, status.Error(codes.InvalidArgument, "at least one role update is required") + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + + if err := qs.keeper.EvaluateRoleChange(sdkCtx, req.Key, req.RoleUpdates, req.Approvers); err != nil { + return &types.QueryValidateRoleChangeResponse{Error: err.Error(), Authorized: false}, nil + } + return &types.QueryValidateRoleChangeResponse{Error: "", Authorized: true}, nil +} diff --git a/x/registry/keeper/role_event_query_acceptance_test.go b/x/registry/keeper/role_event_query_acceptance_test.go new file mode 100644 index 0000000000..bf40689a1e --- /dev/null +++ b/x/registry/keeper/role_event_query_acceptance_test.go @@ -0,0 +1,312 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + + abci "github.com/cometbft/cometbft/abci/types" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + + "cosmossdk.io/x/nft" + nftkeeper "cosmossdk.io/x/nft/keeper" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/provenance-io/provenance/app" + "github.com/provenance-io/provenance/x/registry/keeper" + "github.com/provenance-io/provenance/x/registry/types" +) + +// RoleEventAndQueryAcceptanceTestSuite covers Phase C4 (the comprehensive EventRoleUpdated, with +// registry_class_id / previous_addresses / signers) and Phase C5 (the read-only ValidateRoleChange +// dry-run query). +type RoleEventAndQueryAcceptanceTestSuite struct { + suite.Suite + + app *app.App + ctx sdk.Context + + registryKeeper keeper.Keeper + nftKeeper nftkeeper.Keeper + msgServer types.MsgServer + queryServer *keeper.QueryServer + + nftClass nft.Class + + nftOwner string + currentController string + newController string + securedParty string + stranger string +} + +func TestRoleEventAndQueryAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(RoleEventAndQueryAcceptanceTestSuite)) +} + +func (s *RoleEventAndQueryAcceptanceTestSuite) SetupTest() { + s.app = app.Setup(s.T()) + s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) + + s.nftKeeper = s.app.NFTKeeper + s.registryKeeper = s.app.RegistryKeeper + s.msgServer = keeper.NewMsgServer(s.registryKeeper) + s.queryServer = keeper.NewQueryServer(s.registryKeeper) + + s.nftOwner = genAddr() + s.currentController = genAddr() + s.newController = genAddr() + s.securedParty = genAddr() + s.stranger = genAddr() + + s.nftClass = nft.Class{Id: "event-query-test-nft-class"} + s.nftKeeper.SaveClass(s.ctx, s.nftClass) + + // Install the CONTROLLER policy as the module default so classless entries are policy-governed. + s.Require().NoError(s.registryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) +} + +func (s *RoleEventAndQueryAcceptanceTestSuite) mintNFT(id string) *types.RegistryKey { + n := nft.NFT{ClassId: s.nftClass.Id, Id: id} + ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) + s.Require().NoError(err) + s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) + return &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} +} + +// resetEvents installs a fresh event manager so each scenario inspects only its own events. +func (s *RoleEventAndQueryAcceptanceTestSuite) resetEvents() { + s.ctx = s.ctx.WithEventManager(sdk.NewEventManager()) +} + +// roleUpdatedEvents returns all EventRoleUpdated emitted since the last resetEvents. +func (s *RoleEventAndQueryAcceptanceTestSuite) roleUpdatedEvents() []*types.EventRoleUpdated { + var out []*types.EventRoleUpdated + for _, e := range s.ctx.EventManager().Events() { + if e.Type != "provenance.registry.v1.EventRoleUpdated" { + continue + } + msg, err := sdk.ParseTypedEvent(abci.Event{Type: e.Type, Attributes: e.Attributes}) + s.Require().NoError(err) + ev, ok := msg.(*types.EventRoleUpdated) + s.Require().True(ok) + out = append(out, ev) + } + return out +} + +// --- Phase C4: EventRoleUpdated ----------------------------------------------------------------- + +// TestEventRoleUpdated_LegacyPath: a role change authorized by the NFT owner (no policy for the +// role) emits EventRoleUpdated with the NFT-owner signer and an empty registry_class_id. +func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_LegacyPath() { + key := s.mintNFT("evt-legacy") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, nil, "")) + grantee := genAddr() + + s.resetEvents() + // SERVICER has no policy (only CONTROLLER does), so this uses the NFT-owner fallback. + _, err := s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.nftOwner, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{grantee}, + }) + s.Require().NoError(err) + + evts := s.roleUpdatedEvents() + s.Require().Len(evts, 1) + ev := evts[0] + s.Require().Equal(key.NftId, ev.NftId) + s.Require().Equal(key.AssetClassId, ev.AssetClassId) + s.Require().Equal("", ev.RegistryClassId) + s.Require().Equal("SERVICER", ev.Role) + s.Require().Equal([]string{grantee}, ev.Addresses) + s.Require().Empty(ev.PreviousAddresses) + s.Require().Len(ev.Signers, 1) + s.Require().Equal("NFT_ROLE_NFT_OWNER", ev.Signers[0].Role) + s.Require().Equal("ASSIGNMENT_CURRENT", ev.Signers[0].Assignment) + s.Require().Equal([]string{s.nftOwner}, ev.Signers[0].Addresses) +} + +// TestEventRoleUpdated_PolicyAccumulation: a CONTROLLER change applied via the propose/approve flow +// emits EventRoleUpdated whose signers describe the satisfying policy path (current + secured party +// + new controller) and whose previous/current addresses capture the transition. +func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_PolicyAccumulation() { + key := s.mintNFT("evt-policy") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.currentController}}, + {Role: types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE, Addresses: []string{s.securedParty}}, + }, "")) + + resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ + Signer: s.currentController, + Key: key, + RoleUpdates: []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: []string{s.newController}, + }}, + }) + s.Require().NoError(err) + s.Require().False(resp.Applied) + + _, err = s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{Signer: s.securedParty, ChangeId: resp.ChangeId}) + s.Require().NoError(err) + + // The applying approval emits the EventRoleUpdated; isolate it. + s.resetEvents() + applyResp, err := s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{Signer: s.newController, ChangeId: resp.ChangeId}) + s.Require().NoError(err) + s.Require().True(applyResp.Applied) + + evts := s.roleUpdatedEvents() + s.Require().Len(evts, 1) + ev := evts[0] + s.Require().Equal("CONTROLLER", ev.Role) + s.Require().Equal([]string{s.newController}, ev.Addresses) + s.Require().Equal([]string{s.currentController}, ev.PreviousAddresses) + + // Signers: current controller (CURRENT), secured party for eNote (CURRENT), new controller (NEW). + got := map[string][]string{} + for _, sgn := range ev.Signers { + got[sgn.Role+"|"+sgn.Assignment] = sgn.Addresses + } + s.Require().Equal([]string{s.currentController}, got["REGISTRY_ROLE_CONTROLLER|ASSIGNMENT_CURRENT"]) + s.Require().Equal([]string{s.securedParty}, got["REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE|ASSIGNMENT_CURRENT"]) + s.Require().Equal([]string{s.newController}, got["REGISTRY_ROLE_CONTROLLER|ASSIGNMENT_NEW"]) +} + +// TestEventRoleUpdated_RegistryClassId: an entry registered under a class carries that class id on +// the event. +func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_RegistryClassId() { + maintainer := genAddr() + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: maintainer, + RegistryClassId: "evt-class", + AssetClassId: s.nftClass.Id, + Maintainer: maintainer, + RoleAuthorizations: []types.RoleAuthorization{servicerRoleAuthorization()}, + }) + s.Require().NoError(err) + + key := s.mintNFT("evt-classed") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.currentController}}, + }, "evt-class")) + + grantee := genAddr() + s.resetEvents() + // The class policy lets the current controller manage servicers single-handedly. + _, err = s.msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.currentController, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{grantee}, + }) + s.Require().NoError(err) + + evts := s.roleUpdatedEvents() + s.Require().Len(evts, 1) + ev := evts[0] + s.Require().Equal("evt-class", ev.RegistryClassId) + s.Require().Equal("SERVICER", ev.Role) + s.Require().Empty(ev.PreviousAddresses) + s.Require().Equal([]string{grantee}, ev.Addresses) + s.Require().Len(ev.Signers, 1) + s.Require().Equal("REGISTRY_ROLE_CONTROLLER", ev.Signers[0].Role) + s.Require().Equal("ASSIGNMENT_CURRENT", ev.Signers[0].Assignment) + s.Require().Equal([]string{s.currentController}, ev.Signers[0].Addresses) +} + +// --- Phase C5: ValidateRoleChange query --------------------------------------------------------- + +func (s *RoleEventAndQueryAcceptanceTestSuite) controllerUpdate() []types.RoleUpdate { + return []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + Addresses: []string{s.newController}, + }} +} + +func (s *RoleEventAndQueryAcceptanceTestSuite) TestValidateRoleChange_Authorized() { + key := s.mintNFT("vrc-ok") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.currentController}}, + }, "")) + + res, err := s.queryServer.ValidateRoleChange(s.ctx, &types.QueryValidateRoleChangeRequest{ + Key: key, + RoleUpdates: s.controllerUpdate(), + Approvers: []string{s.currentController, s.newController}, + }) + s.Require().NoError(err) + s.Require().True(res.Authorized) + s.Require().Empty(res.Error) +} + +func (s *RoleEventAndQueryAcceptanceTestSuite) TestValidateRoleChange_MissingApprover() { + key := s.mintNFT("vrc-missing") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.currentController}}, + }, "")) + + // Only the current controller approves; the new controller's approval is still outstanding. + res, err := s.queryServer.ValidateRoleChange(s.ctx, &types.QueryValidateRoleChangeRequest{ + Key: key, + RoleUpdates: s.controllerUpdate(), + Approvers: []string{s.currentController}, + }) + s.Require().NoError(err) + s.Require().False(res.Authorized) + s.Require().NotEmpty(res.Error) + s.Require().Contains(res.Error, "CONTROLLER") +} + +func (s *RoleEventAndQueryAcceptanceTestSuite) TestValidateRoleChange_LegacyFallback() { + key := s.mintNFT("vrc-legacy") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, nil, "")) + + update := []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{genAddr()}, + }} + + s.Run("NFT owner approves", func() { + res, err := s.queryServer.ValidateRoleChange(s.ctx, &types.QueryValidateRoleChangeRequest{ + Key: key, + RoleUpdates: update, + Approvers: []string{s.nftOwner}, + }) + s.Require().NoError(err) + s.Require().True(res.Authorized) + s.Require().Empty(res.Error) + }) + + s.Run("non-owner is rejected", func() { + res, err := s.queryServer.ValidateRoleChange(s.ctx, &types.QueryValidateRoleChangeRequest{ + Key: key, + RoleUpdates: update, + Approvers: []string{s.stranger}, + }) + s.Require().NoError(err) + s.Require().False(res.Authorized) + s.Require().NotEmpty(res.Error) + }) +} + +func (s *RoleEventAndQueryAcceptanceTestSuite) TestValidateRoleChange_InvalidRequest() { + s.Run("nil request", func() { + _, err := s.queryServer.ValidateRoleChange(s.ctx, nil) + s.Require().Error(err) + }) + + s.Run("no role updates", func() { + key := s.mintNFT("vrc-empty") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, nil, "")) + _, err := s.queryServer.ValidateRoleChange(s.ctx, &types.QueryValidateRoleChangeRequest{Key: key}) + s.Require().Error(err) + }) +} diff --git a/x/registry/types/events.go b/x/registry/types/events.go index 36a7adb22b..d28cf31fdf 100644 --- a/x/registry/types/events.go +++ b/x/registry/types/events.go @@ -98,6 +98,30 @@ func NewEventParamsUpdated() *EventParamsUpdated { return &EventParamsUpdated{} } +// NewEventRoleUpdated returns a new EventRoleUpdated describing the full state transition for a +// single role, including the addresses before and after and the signers that authorized it. +func NewEventRoleUpdated(key *RegistryKey, registryClassID string, role RegistryRole, previousAddrs, currentAddrs []string, signers []RoleSigner) *EventRoleUpdated { + return &EventRoleUpdated{ + NftId: key.NftId, + AssetClassId: key.AssetClassId, + RegistryClassId: registryClassID, + Role: role.ShortString(), + Addresses: currentAddrs, + PreviousAddresses: previousAddrs, + Signers: signers, + } +} + +// NewNFTOwnerSigner returns the RoleSigner describing the legacy NFT-owner authorization path, where +// the NFT owner's signature alone authorizes a role change for a role with no configured policy. +func NewNFTOwnerSigner(owner string) RoleSigner { + return RoleSigner{ + Role: NftRole_NFT_ROLE_NFT_OWNER.String(), + Assignment: Assignment_ASSIGNMENT_CURRENT.String(), + Addresses: []string{owner}, + } +} + // GetChangeEvents gets all the events that represent the changes from oldReg to newReg. // Panics if they have different keys (unless oldReg or newReg is nil). func GetChangeEvents(oldReg, newReg *RegistryEntry) ([]*EventRoleGranted, []*EventRoleRevoked) { diff --git a/x/registry/types/events.pb.go b/x/registry/types/events.pb.go index d43c0aca91..55dfeb2397 100644 --- a/x/registry/types/events.pb.go +++ b/x/registry/types/events.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -653,6 +654,173 @@ func (m *EventParamsUpdated) XXX_DiscardUnknown() { var xxx_messageInfo_EventParamsUpdated proto.InternalMessageInfo +// RoleSigner describes a role/assignment whose resolved addresses contributed a signature toward +// authorizing a role change. It is the structured form of an entry in EventRoleUpdated.signers. +type RoleSigner struct { + // role is the (registry or NFT) role whose holders provided the signature(s). + Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"` + // assignment is the assignment variant the role was resolved under (e.g. ASSIGNMENT_CURRENT). + Assignment string `protobuf:"bytes,2,opt,name=assignment,proto3" json:"assignment,omitempty"` + // addresses are the resolved addresses for the role that signed to authorize the change. + Addresses []string `protobuf:"bytes,3,rep,name=addresses,proto3" json:"addresses,omitempty"` +} + +func (m *RoleSigner) Reset() { *m = RoleSigner{} } +func (m *RoleSigner) String() string { return proto.CompactTextString(m) } +func (*RoleSigner) ProtoMessage() {} +func (*RoleSigner) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{11} +} +func (m *RoleSigner) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RoleSigner) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RoleSigner.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RoleSigner) XXX_Merge(src proto.Message) { + xxx_messageInfo_RoleSigner.Merge(m, src) +} +func (m *RoleSigner) XXX_Size() int { + return m.Size() +} +func (m *RoleSigner) XXX_DiscardUnknown() { + xxx_messageInfo_RoleSigner.DiscardUnknown(m) +} + +var xxx_messageInfo_RoleSigner proto.InternalMessageInfo + +func (m *RoleSigner) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *RoleSigner) GetAssignment() string { + if m != nil { + return m.Assignment + } + return "" +} + +func (m *RoleSigner) GetAddresses() []string { + if m != nil { + return m.Addresses + } + return nil +} + +// EventRoleUpdated is the comprehensive event emitted whenever a role is granted, revoked, or set +// for a registered NFT. It is emitted for MsgGrantRole, MsgRevokeRole, and once per updated role +// for MsgSetRoles (including changes applied via the propose/approve accumulation flow). +type EventRoleUpdated struct { + // nft_id is the ID of the NFT for which the role was updated. + NftId string `protobuf:"bytes,1,opt,name=nft_id,json=nftId,proto3" json:"nft_id,omitempty"` + // asset_class_id is the ID of the asset class the NFT belongs to. + AssetClassId string `protobuf:"bytes,2,opt,name=asset_class_id,json=assetClassId,proto3" json:"asset_class_id,omitempty"` + // registry_class_id is the registry class governing this NFT (empty if none). + RegistryClassId string `protobuf:"bytes,3,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` + // role is the role that was updated. + Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` + // addresses are the addresses assigned to the role after this operation (empty if cleared). + Addresses []string `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` + // previous_addresses are the addresses assigned to the role before this operation. + PreviousAddresses []string `protobuf:"bytes,6,rep,name=previous_addresses,json=previousAddresses,proto3" json:"previous_addresses,omitempty"` + // signers describes the roles/addresses that authorized this change. + Signers []RoleSigner `protobuf:"bytes,7,rep,name=signers,proto3" json:"signers"` +} + +func (m *EventRoleUpdated) Reset() { *m = EventRoleUpdated{} } +func (m *EventRoleUpdated) String() string { return proto.CompactTextString(m) } +func (*EventRoleUpdated) ProtoMessage() {} +func (*EventRoleUpdated) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{12} +} +func (m *EventRoleUpdated) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRoleUpdated) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRoleUpdated.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRoleUpdated) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRoleUpdated.Merge(m, src) +} +func (m *EventRoleUpdated) XXX_Size() int { + return m.Size() +} +func (m *EventRoleUpdated) XXX_DiscardUnknown() { + xxx_messageInfo_EventRoleUpdated.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRoleUpdated proto.InternalMessageInfo + +func (m *EventRoleUpdated) GetNftId() string { + if m != nil { + return m.NftId + } + return "" +} + +func (m *EventRoleUpdated) GetAssetClassId() string { + if m != nil { + return m.AssetClassId + } + return "" +} + +func (m *EventRoleUpdated) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +func (m *EventRoleUpdated) GetRole() string { + if m != nil { + return m.Role + } + return "" +} + +func (m *EventRoleUpdated) GetAddresses() []string { + if m != nil { + return m.Addresses + } + return nil +} + +func (m *EventRoleUpdated) GetPreviousAddresses() []string { + if m != nil { + return m.PreviousAddresses + } + return nil +} + +func (m *EventRoleUpdated) GetSigners() []RoleSigner { + if m != nil { + return m.Signers + } + return nil +} + func init() { proto.RegisterType((*EventNFTRegistered)(nil), "provenance.registry.v1.EventNFTRegistered") proto.RegisterType((*EventRoleGranted)(nil), "provenance.registry.v1.EventRoleGranted") @@ -665,6 +833,8 @@ func init() { proto.RegisterType((*EventRegistryClassCreated)(nil), "provenance.registry.v1.EventRegistryClassCreated") proto.RegisterType((*EventRegistryClassUpdated)(nil), "provenance.registry.v1.EventRegistryClassUpdated") proto.RegisterType((*EventParamsUpdated)(nil), "provenance.registry.v1.EventParamsUpdated") + proto.RegisterType((*RoleSigner)(nil), "provenance.registry.v1.RoleSigner") + proto.RegisterType((*EventRoleUpdated)(nil), "provenance.registry.v1.EventRoleUpdated") } func init() { @@ -672,36 +842,43 @@ func init() { } var fileDescriptor_61a0995529587ff0 = []byte{ - // 453 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x54, 0x5d, 0x6f, 0xd3, 0x30, - 0x14, 0xad, 0xb7, 0x32, 0xad, 0x57, 0x88, 0x0f, 0x6b, 0x8c, 0x0c, 0x50, 0x34, 0x05, 0x1e, 0x26, - 0x24, 0x12, 0x4d, 0xf0, 0x07, 0x58, 0x05, 0xa8, 0x2f, 0xa8, 0x04, 0x26, 0x24, 0x5e, 0x26, 0xaf, - 0xbe, 0xeb, 0xac, 0xa6, 0x76, 0x74, 0xed, 0x45, 0xec, 0x91, 0x1f, 0x80, 0xe0, 0x67, 0xf1, 0xb8, - 0x47, 0x1e, 0x51, 0xfb, 0x47, 0x50, 0x9d, 0x66, 0x69, 0xc9, 0x1e, 0x40, 0x05, 0xde, 0xe2, 0x73, - 0x8f, 0xcf, 0x3d, 0x3e, 0x76, 0x2e, 0x3c, 0xcc, 0xc9, 0x14, 0xa8, 0x85, 0x1e, 0x60, 0x42, 0x38, - 0x54, 0xd6, 0xd1, 0x79, 0x52, 0xec, 0x27, 0x58, 0xa0, 0x76, 0x36, 0xce, 0xc9, 0x38, 0xc3, 0xb7, - 0x6b, 0x52, 0x5c, 0x91, 0xe2, 0x62, 0x3f, 0x7a, 0x03, 0xfc, 0xc5, 0x8c, 0xf7, 0xfa, 0xe5, 0xbb, - 0xd4, 0xc3, 0x48, 0x28, 0xf9, 0x1d, 0xd8, 0xd0, 0x27, 0xee, 0x48, 0xc9, 0x80, 0xed, 0xb2, 0xbd, - 0x4e, 0x7a, 0x4d, 0x9f, 0xb8, 0x9e, 0xe4, 0x8f, 0xe0, 0x86, 0xb0, 0x16, 0xdd, 0xd1, 0x20, 0x13, - 0xd6, 0xce, 0xca, 0x6b, 0xbe, 0x7c, 0xdd, 0xa3, 0xdd, 0x19, 0xd8, 0x93, 0xd1, 0x27, 0x06, 0xb7, - 0xbc, 0x66, 0x6a, 0x32, 0x7c, 0x45, 0x42, 0xbb, 0x15, 0x15, 0x39, 0x87, 0x36, 0x99, 0x0c, 0x83, - 0x75, 0x5f, 0xf3, 0xdf, 0xfc, 0x01, 0x74, 0x84, 0x94, 0x84, 0xd6, 0xa2, 0x0d, 0xda, 0xbb, 0xeb, - 0x7b, 0x9d, 0xb4, 0x06, 0x96, 0x3d, 0xa4, 0x58, 0x98, 0xd1, 0xff, 0xf7, 0xf0, 0x16, 0xb6, 0xaa, - 0x68, 0x0f, 0x35, 0xfd, 0xa5, 0x70, 0xdf, 0x43, 0x50, 0x9e, 0x6b, 0x7e, 0x87, 0x07, 0x67, 0xd9, - 0xe8, 0x30, 0x97, 0x62, 0xd5, 0x8c, 0xa3, 0x2f, 0x0c, 0xee, 0x5e, 0x26, 0xd6, 0x3d, 0x15, 0x7a, - 0x88, 0x7d, 0x32, 0xb9, 0xb1, 0xab, 0x06, 0x77, 0x1f, 0x3a, 0x03, 0x2f, 0x37, 0x23, 0x94, 0xe9, - 0x6d, 0x96, 0x40, 0x4f, 0xf2, 0x7b, 0xb0, 0x99, 0x97, 0x5d, 0x28, 0x68, 0x97, 0xb5, 0x6a, 0x1d, - 0xa5, 0x0d, 0x43, 0xcf, 0x73, 0xff, 0x8a, 0x7f, 0xd1, 0x64, 0x4d, 0x4d, 0x51, 0x12, 0x69, 0x6e, - 0xe8, 0x72, 0x1d, 0x11, 0x6c, 0x37, 0x35, 0x33, 0xf5, 0x2f, 0xcf, 0x18, 0x7d, 0x66, 0xb0, 0xb3, - 0x74, 0x67, 0x7e, 0x57, 0x97, 0xd0, 0x5f, 0xda, 0x63, 0xb8, 0x5d, 0xfd, 0x8f, 0x75, 0x8f, 0xd2, - 0xc2, 0x4d, 0x5a, 0xdc, 0xf0, 0xdb, 0x66, 0x42, 0x80, 0xb1, 0x50, 0xda, 0x09, 0xa5, 0x91, 0xe6, - 0x6e, 0x16, 0x90, 0x68, 0x78, 0x95, 0x9d, 0xea, 0x0d, 0xfd, 0x89, 0x9d, 0xe5, 0x46, 0x6b, 0x8d, - 0x46, 0x5b, 0xf3, 0xd9, 0xd2, 0x17, 0x24, 0xc6, 0x55, 0x87, 0x83, 0xd1, 0xb7, 0x49, 0xc8, 0x2e, - 0x26, 0x21, 0xfb, 0x31, 0x09, 0xd9, 0xd7, 0x69, 0xd8, 0xba, 0x98, 0x86, 0xad, 0xef, 0xd3, 0xb0, - 0x05, 0x3b, 0xca, 0xc4, 0x57, 0x8f, 0xa9, 0x3e, 0xfb, 0xf0, 0x6c, 0xa8, 0xdc, 0xe9, 0xd9, 0x71, - 0x3c, 0x30, 0xe3, 0xa4, 0x26, 0x3d, 0x51, 0x66, 0x61, 0x95, 0x7c, 0xac, 0x07, 0xa0, 0x3b, 0xcf, - 0xd1, 0x1e, 0x6f, 0xf8, 0xe9, 0xf7, 0xf4, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0x77, 0x1c, 0x1c, - 0x15, 0x24, 0x05, 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0xae, 0xdb, 0xae, 0x5b, 0x0f, 0x08, 0x58, 0x54, 0x46, 0x36, 0x50, 0xa8, 0x02, 0x17, 0x15, + 0xd2, 0x12, 0x0d, 0x78, 0x81, 0xb5, 0x02, 0xd4, 0x1b, 0x54, 0x32, 0x26, 0x24, 0x2e, 0xa8, 0xbc, + 0xc6, 0xcb, 0xac, 0xb6, 0x76, 0x64, 0xbb, 0x11, 0xbb, 0xe4, 0x01, 0x10, 0x3c, 0x02, 0x8f, 0xb3, + 0xcb, 0x5d, 0x72, 0x85, 0x50, 0xfb, 0x22, 0x28, 0x4e, 0xd3, 0xa4, 0x3f, 0x93, 0x40, 0x1d, 0xbb, + 0x8b, 0xcf, 0xf9, 0xce, 0x77, 0x3e, 0x7f, 0xce, 0xb1, 0xe1, 0x49, 0x28, 0x78, 0x44, 0x18, 0x66, + 0x3d, 0xe2, 0x0a, 0x12, 0x50, 0xa9, 0xc4, 0xb9, 0x1b, 0x1d, 0xb8, 0x24, 0x22, 0x4c, 0x49, 0x27, + 0x14, 0x5c, 0x71, 0x63, 0x27, 0x03, 0x39, 0x29, 0xc8, 0x89, 0x0e, 0xf6, 0x6a, 0x01, 0x0f, 0xb8, + 0x86, 0xb8, 0xf1, 0x57, 0x82, 0xb6, 0xdf, 0x81, 0xf1, 0x2a, 0xae, 0x7e, 0xfb, 0xfa, 0xbd, 0xa7, + 0xc1, 0x44, 0x10, 0xdf, 0xb8, 0x0f, 0x15, 0x76, 0xaa, 0xba, 0xd4, 0x37, 0x51, 0x1d, 0x35, 0xaa, + 0xde, 0x06, 0x3b, 0x55, 0x6d, 0xdf, 0x78, 0x0a, 0x77, 0xb0, 0x94, 0x44, 0x75, 0x7b, 0x03, 0x2c, + 0x65, 0x9c, 0x2e, 0xea, 0xf4, 0x6d, 0x1d, 0x6d, 0xc5, 0xc1, 0xb6, 0x6f, 0x7f, 0x41, 0x70, 0x4f, + 0x73, 0x7a, 0x7c, 0x40, 0xde, 0x08, 0xcc, 0xd4, 0x9a, 0x8c, 0x86, 0x01, 0x65, 0xc1, 0x07, 0xc4, + 0x2c, 0xe9, 0x9c, 0xfe, 0x36, 0x1e, 0x41, 0x15, 0xfb, 0xbe, 0x20, 0x52, 0x12, 0x69, 0x96, 0xeb, + 0xa5, 0x46, 0xd5, 0xcb, 0x02, 0xf3, 0x1a, 0x3c, 0x12, 0xf1, 0xfe, 0xcd, 0x6b, 0x38, 0x82, 0x5a, + 0x6a, 0xed, 0x31, 0x13, 0xd7, 0x64, 0xee, 0x07, 0x30, 0x93, 0x7d, 0x4d, 0x4f, 0xb6, 0x39, 0x1a, + 0xf4, 0x8f, 0x43, 0x1f, 0xaf, 0xeb, 0xb1, 0xfd, 0x0d, 0xc1, 0x83, 0x99, 0x63, 0xad, 0x33, 0xcc, + 0x02, 0xd2, 0x11, 0x3c, 0xe4, 0x72, 0x5d, 0xe3, 0x1e, 0x42, 0xb5, 0xa7, 0xe9, 0x62, 0x40, 0xe2, + 0xde, 0x56, 0x12, 0x68, 0xfb, 0xc6, 0x1e, 0x6c, 0x85, 0x49, 0x17, 0x61, 0x96, 0x93, 0x5c, 0xba, + 0xb6, 0xbd, 0x25, 0x41, 0x87, 0xa1, 0xfe, 0xb7, 0x17, 0x38, 0xd1, 0x32, 0x27, 0x4e, 0x80, 0x62, + 0x2a, 0x68, 0xb6, 0xb6, 0x05, 0xec, 0x2c, 0x73, 0x0e, 0xe8, 0xff, 0xdc, 0xa3, 0xfd, 0x15, 0xc1, + 0xee, 0xdc, 0x99, 0xe9, 0xaa, 0x96, 0x20, 0xfa, 0xd0, 0x9e, 0xc1, 0x76, 0x3a, 0xa5, 0x59, 0x8f, + 0x44, 0xc2, 0x5d, 0x91, 0x2f, 0xf8, 0x6b, 0x31, 0x16, 0xc0, 0x10, 0x53, 0xa6, 0x30, 0x65, 0x44, + 0x4c, 0xd5, 0xe4, 0x22, 0x76, 0xb0, 0x4a, 0x4e, 0xfa, 0x0f, 0xfd, 0x8b, 0x9c, 0xf9, 0x46, 0xc5, + 0xa5, 0x46, 0xb5, 0xe9, 0xdd, 0xd2, 0xc1, 0x02, 0x0f, 0xd3, 0x0e, 0xf6, 0x27, 0x80, 0xd8, 0xfd, + 0x23, 0x1a, 0x30, 0x22, 0x66, 0x63, 0x85, 0x72, 0x63, 0x65, 0x01, 0x60, 0x29, 0x69, 0xc0, 0x86, + 0x84, 0xa9, 0x94, 0x37, 0x8b, 0xcc, 0x8f, 0x5d, 0x69, 0x71, 0xec, 0x7e, 0x14, 0x73, 0xa3, 0x7f, + 0x1d, 0xa3, 0xb1, 0xda, 0x93, 0xd2, 0x6a, 0x4f, 0xd2, 0xfd, 0x94, 0xaf, 0xba, 0x26, 0x36, 0x16, + 0xf4, 0x1a, 0xfb, 0x60, 0x84, 0x82, 0x44, 0x94, 0x8f, 0x64, 0x37, 0x83, 0x55, 0x34, 0x6c, 0x3b, + 0xcd, 0x1c, 0xce, 0xe0, 0x4d, 0xd8, 0x94, 0xda, 0x3a, 0x69, 0x6e, 0xd6, 0x4b, 0x8d, 0x5b, 0xcf, + 0x6d, 0x67, 0xf5, 0x85, 0xef, 0x64, 0x2e, 0x37, 0xcb, 0x17, 0xbf, 0x1e, 0x17, 0xbc, 0xb4, 0xb0, + 0xd9, 0xbf, 0x18, 0x5b, 0xe8, 0x72, 0x6c, 0xa1, 0xdf, 0x63, 0x0b, 0x7d, 0x9f, 0x58, 0x85, 0xcb, + 0x89, 0x55, 0xf8, 0x39, 0xb1, 0x0a, 0xb0, 0x4b, 0xf9, 0x15, 0x74, 0x1d, 0xf4, 0xf1, 0x65, 0x40, + 0xd5, 0xd9, 0xe8, 0xc4, 0xe9, 0xf1, 0xa1, 0x9b, 0x81, 0xf6, 0x29, 0xcf, 0xad, 0xdc, 0xcf, 0xd9, + 0xcb, 0xa4, 0xce, 0x43, 0x22, 0x4f, 0x2a, 0xfa, 0xa1, 0x79, 0xf1, 0x27, 0x00, 0x00, 0xff, 0xff, + 0x23, 0xb6, 0xf0, 0xd0, 0xbd, 0x06, 0x00, 0x00, } func (m *EventNFTRegistered) Marshal() (dAtA []byte, err error) { @@ -1157,6 +1334,135 @@ func (m *EventParamsUpdated) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *RoleSigner) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RoleSigner) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RoleSigner) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Addresses) > 0 { + for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addresses[iNdEx]) + copy(dAtA[i:], m.Addresses[iNdEx]) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.Assignment) > 0 { + i -= len(m.Assignment) + copy(dAtA[i:], m.Assignment) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Assignment))) + i-- + dAtA[i] = 0x12 + } + if len(m.Role) > 0 { + i -= len(m.Role) + copy(dAtA[i:], m.Role) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Role))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *EventRoleUpdated) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRoleUpdated) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRoleUpdated) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signers) > 0 { + for iNdEx := len(m.Signers) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Signers[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintEvents(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + } + } + if len(m.PreviousAddresses) > 0 { + for iNdEx := len(m.PreviousAddresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.PreviousAddresses[iNdEx]) + copy(dAtA[i:], m.PreviousAddresses[iNdEx]) + i = encodeVarintEvents(dAtA, i, uint64(len(m.PreviousAddresses[iNdEx]))) + i-- + dAtA[i] = 0x32 + } + } + if len(m.Addresses) > 0 { + for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Addresses[iNdEx]) + copy(dAtA[i:], m.Addresses[iNdEx]) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Addresses[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } + if len(m.Role) > 0 { + i -= len(m.Role) + copy(dAtA[i:], m.Role) + i = encodeVarintEvents(dAtA, i, uint64(len(m.Role))) + i-- + dAtA[i] = 0x22 + } + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0x1a + } + if len(m.AssetClassId) > 0 { + i -= len(m.AssetClassId) + copy(dAtA[i:], m.AssetClassId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.AssetClassId))) + i-- + dAtA[i] = 0x12 + } + if len(m.NftId) > 0 { + i -= len(m.NftId) + copy(dAtA[i:], m.NftId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.NftId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintEvents(dAtA []byte, offset int, v uint64) int { offset -= sovEvents(v) base := offset @@ -1383,48 +1689,114 @@ func (m *EventParamsUpdated) Size() (n int) { return n } -func sovEvents(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 -} -func sozEvents(x uint64) (n int) { - return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *EventNFTRegistered) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: EventNFTRegistered: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: EventNFTRegistered: illegal tag %d (wire type %d)", fieldNum, wire) +func (m *RoleSigner) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Role) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Assignment) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { + l = len(s) + n += 1 + l + sovEvents(uint64(l)) } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { + } + return n +} + +func (m *EventRoleUpdated) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.NftId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.AssetClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.Role) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + if len(m.Addresses) > 0 { + for _, s := range m.Addresses { + l = len(s) + n += 1 + l + sovEvents(uint64(l)) + } + } + if len(m.PreviousAddresses) > 0 { + for _, s := range m.PreviousAddresses { + l = len(s) + n += 1 + l + sovEvents(uint64(l)) + } + } + if len(m.Signers) > 0 { + for _, e := range m.Signers { + l = e.Size() + n += 1 + l + sovEvents(uint64(l)) + } + } + return n +} + +func sovEvents(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozEvents(x uint64) (n int) { + return sovEvents(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *EventNFTRegistered) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventNFTRegistered: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventNFTRegistered: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { return ErrIntOverflowEvents } if iNdEx >= l { @@ -2835,6 +3207,428 @@ func (m *EventParamsUpdated) Unmarshal(dAtA []byte) error { } return nil } +func (m *RoleSigner) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RoleSigner: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RoleSigner: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Role = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Assignment", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Assignment = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *EventRoleUpdated) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRoleUpdated: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRoleUpdated: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NftId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NftId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AssetClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AssetClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Role", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Role = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Addresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Addresses = append(m.Addresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PreviousAddresses", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PreviousAddresses = append(m.PreviousAddresses, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signers", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signers = append(m.Signers, RoleSigner{}) + if err := m.Signers[len(m.Signers)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipEvents(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/query.pb.go b/x/registry/types/query.pb.go index 09edc6478a..a0df21dbbe 100644 --- a/x/registry/types/query.pb.go +++ b/x/registry/types/query.pb.go @@ -836,6 +836,127 @@ func (m *QueryParamsResponse) GetParams() Params { return Params{} } +// QueryValidateRoleChangeRequest is the request type for the Query/ValidateRoleChange RPC method. +// It describes a candidate role-change batch and the set of approvers to test against each affected +// role's authorization policy. +type QueryValidateRoleChangeRequest struct { + // key identifies the registry entry the role change targets. + Key *RegistryKey `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // role_updates is the desired-state batch to evaluate (same shape as MsgSetRoles). + RoleUpdates []RoleUpdate `protobuf:"bytes,2,rep,name=role_updates,json=roleUpdates,proto3" json:"role_updates"` + // approvers is the accumulated/candidate set of approver addresses to test. + Approvers []string `protobuf:"bytes,3,rep,name=approvers,proto3" json:"approvers,omitempty"` +} + +func (m *QueryValidateRoleChangeRequest) Reset() { *m = QueryValidateRoleChangeRequest{} } +func (m *QueryValidateRoleChangeRequest) String() string { return proto.CompactTextString(m) } +func (*QueryValidateRoleChangeRequest) ProtoMessage() {} +func (*QueryValidateRoleChangeRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{16} +} +func (m *QueryValidateRoleChangeRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryValidateRoleChangeRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryValidateRoleChangeRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryValidateRoleChangeRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryValidateRoleChangeRequest.Merge(m, src) +} +func (m *QueryValidateRoleChangeRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryValidateRoleChangeRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryValidateRoleChangeRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryValidateRoleChangeRequest proto.InternalMessageInfo + +func (m *QueryValidateRoleChangeRequest) GetKey() *RegistryKey { + if m != nil { + return m.Key + } + return nil +} + +func (m *QueryValidateRoleChangeRequest) GetRoleUpdates() []RoleUpdate { + if m != nil { + return m.RoleUpdates + } + return nil +} + +func (m *QueryValidateRoleChangeRequest) GetApprovers() []string { + if m != nil { + return m.Approvers + } + return nil +} + +// QueryValidateRoleChangeResponse is the response type for the Query/ValidateRoleChange RPC method. +type QueryValidateRoleChangeResponse struct { + // error contains the authorization failure explanation. It is empty when the change is authorized. + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + // authorized is true if the supplied approvers satisfy every affected role's policy. + Authorized bool `protobuf:"varint,2,opt,name=authorized,proto3" json:"authorized,omitempty"` +} + +func (m *QueryValidateRoleChangeResponse) Reset() { *m = QueryValidateRoleChangeResponse{} } +func (m *QueryValidateRoleChangeResponse) String() string { return proto.CompactTextString(m) } +func (*QueryValidateRoleChangeResponse) ProtoMessage() {} +func (*QueryValidateRoleChangeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_c166c561e401a2eb, []int{17} +} +func (m *QueryValidateRoleChangeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryValidateRoleChangeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryValidateRoleChangeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *QueryValidateRoleChangeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryValidateRoleChangeResponse.Merge(m, src) +} +func (m *QueryValidateRoleChangeResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryValidateRoleChangeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryValidateRoleChangeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryValidateRoleChangeResponse proto.InternalMessageInfo + +func (m *QueryValidateRoleChangeResponse) GetError() string { + if m != nil { + return m.Error + } + return "" +} + +func (m *QueryValidateRoleChangeResponse) GetAuthorized() bool { + if m != nil { + return m.Authorized + } + return false +} + func init() { proto.RegisterType((*QueryGetRegistryRequest)(nil), "provenance.registry.v1.QueryGetRegistryRequest") proto.RegisterType((*QueryGetRegistryResponse)(nil), "provenance.registry.v1.QueryGetRegistryResponse") @@ -853,6 +974,8 @@ func init() { proto.RegisterType((*QueryRegistryClassesResponse)(nil), "provenance.registry.v1.QueryRegistryClassesResponse") proto.RegisterType((*QueryParamsRequest)(nil), "provenance.registry.v1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "provenance.registry.v1.QueryParamsResponse") + proto.RegisterType((*QueryValidateRoleChangeRequest)(nil), "provenance.registry.v1.QueryValidateRoleChangeRequest") + proto.RegisterType((*QueryValidateRoleChangeResponse)(nil), "provenance.registry.v1.QueryValidateRoleChangeResponse") } func init() { @@ -860,70 +983,77 @@ func init() { } var fileDescriptor_c166c561e401a2eb = []byte{ - // 993 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcf, 0x6f, 0x1b, 0x45, - 0x14, 0xce, 0x24, 0x25, 0x09, 0x2f, 0x24, 0x25, 0xd3, 0x08, 0x1c, 0x53, 0xb6, 0xd6, 0xf6, 0x07, - 0xc1, 0x2d, 0x3b, 0xb1, 0x9b, 0x42, 0x85, 0x7a, 0x4a, 0x81, 0x50, 0x7a, 0x09, 0x5b, 0xc1, 0x01, - 0x0e, 0xd6, 0xc4, 0x1e, 0xd6, 0xab, 0x24, 0x3b, 0xdb, 0x9d, 0x4d, 0x84, 0xb1, 0x72, 0x80, 0x5b, - 0x6f, 0x48, 0xfc, 0x01, 0xf4, 0xc0, 0x99, 0x03, 0x47, 0x40, 0x42, 0xdc, 0x7a, 0x41, 0xaa, 0xc4, - 0x85, 0x13, 0x42, 0x09, 0x7f, 0x48, 0xb5, 0xb3, 0x6f, 0xed, 0x5d, 0xdb, 0x9b, 0xb5, 0x95, 0xdc, - 0x76, 0x67, 0xde, 0xf7, 0xde, 0xf7, 0xbd, 0xf7, 0xfc, 0x9e, 0x17, 0x4c, 0x3f, 0x90, 0x87, 0xc2, - 0xe3, 0x5e, 0x53, 0xb0, 0x40, 0x38, 0xae, 0x0a, 0x83, 0x0e, 0x3b, 0xac, 0xb1, 0xc7, 0x07, 0x22, - 0xe8, 0x58, 0x7e, 0x20, 0x43, 0x49, 0x5f, 0xeb, 0xdb, 0x58, 0x89, 0x8d, 0x75, 0x58, 0x2b, 0x57, - 0x9b, 0x52, 0xed, 0x4b, 0xc5, 0x76, 0xb8, 0x12, 0x31, 0x80, 0x1d, 0xd6, 0x76, 0x44, 0xc8, 0x6b, - 0xcc, 0xe7, 0x8e, 0xeb, 0xf1, 0xd0, 0x95, 0x5e, 0xec, 0xa3, 0xbc, 0xe2, 0x48, 0x47, 0xea, 0x47, - 0x16, 0x3d, 0xe1, 0xe9, 0x65, 0x47, 0x4a, 0x67, 0x4f, 0x30, 0xee, 0xbb, 0x8c, 0x7b, 0x9e, 0x0c, - 0x35, 0x44, 0xe1, 0xed, 0xf5, 0x1c, 0x6e, 0x3d, 0x0e, 0xb1, 0x59, 0x35, 0xc7, 0x8c, 0x1f, 0x84, - 0x6d, 0x19, 0xb8, 0xdf, 0xa4, 0x69, 0x5c, 0xcd, 0xb1, 0xf5, 0x79, 0xc0, 0xf7, 0x31, 0xae, 0xb9, - 0x0d, 0xaf, 0x7f, 0x1a, 0xa9, 0xd9, 0x12, 0xa1, 0x8d, 0x36, 0xb6, 0x78, 0x7c, 0x20, 0x54, 0x48, - 0xef, 0xc0, 0xcc, 0xae, 0xe8, 0x94, 0x48, 0x85, 0xac, 0x2d, 0xd4, 0xaf, 0x5a, 0xa3, 0x13, 0x63, - 0x25, 0xa8, 0x87, 0xa2, 0x63, 0x47, 0xf6, 0x66, 0x13, 0x4a, 0xc3, 0x1e, 0x95, 0x2f, 0x3d, 0x25, - 0xe8, 0x16, 0xcc, 0x27, 0x58, 0xf4, 0x7b, 0xbd, 0xc8, 0xef, 0x87, 0x5e, 0x18, 0x74, 0x36, 0x2f, - 0x3c, 0xfb, 0xf7, 0xca, 0x94, 0xdd, 0x03, 0x9b, 0x4f, 0x08, 0xac, 0x0e, 0x44, 0x71, 0x85, 0x4a, - 0x98, 0x5f, 0x83, 0x25, 0xae, 0x94, 0x08, 0x1b, 0xcd, 0x3d, 0xae, 0x54, 0xc3, 0x6d, 0xe9, 0x60, - 0x2f, 0xdb, 0xaf, 0xe8, 0xd3, 0xfb, 0xd1, 0xe1, 0x83, 0x16, 0xfd, 0x08, 0xa0, 0x5f, 0xba, 0x52, - 0x53, 0xd3, 0xb9, 0x61, 0xc5, 0x75, 0xb6, 0xa2, 0x3a, 0x5b, 0x71, 0x63, 0x60, 0x9d, 0xad, 0x6d, - 0xee, 0x08, 0x8c, 0x60, 0xa7, 0x90, 0xe6, 0x2f, 0x04, 0xca, 0xa3, 0xb8, 0xa0, 0xe6, 0x87, 0x00, - 0x41, 0xef, 0xb4, 0x44, 0x2a, 0x33, 0x93, 0xaa, 0x4e, 0xc1, 0xe9, 0xd6, 0x08, 0xce, 0x6f, 0x15, - 0x72, 0x8e, 0x99, 0x64, 0x48, 0x3f, 0x25, 0x70, 0x49, 0x93, 0xfe, 0x98, 0x2b, 0x5b, 0xee, 0x89, - 0xb3, 0x15, 0x9d, 0x96, 0x60, 0x8e, 0xb7, 0x5a, 0x81, 0x50, 0xaa, 0x34, 0xad, 0x53, 0x9d, 0xbc, - 0xd2, 0xbb, 0x70, 0x21, 0x90, 0x7b, 0xa2, 0x34, 0x53, 0x21, 0x6b, 0x4b, 0xf5, 0x6b, 0x45, 0x1e, - 0x35, 0x17, 0x8d, 0x30, 0x6b, 0xb0, 0x92, 0x65, 0x88, 0x09, 0x5d, 0x85, 0xf9, 0x36, 0x57, 0x0d, - 0xed, 0x35, 0xe2, 0x39, 0x6f, 0xcf, 0xb5, 0x63, 0x13, 0x93, 0xc1, 0x9b, 0x1a, 0xb2, 0x2d, 0xbc, - 0x96, 0xeb, 0x39, 0xd1, 0xd9, 0xfd, 0x36, 0xf7, 0x7a, 0x75, 0xa3, 0x4b, 0x30, 0xdd, 0xeb, 0x86, - 0x69, 0xb7, 0x65, 0x7e, 0x4b, 0xc0, 0xc8, 0x43, 0x60, 0xb8, 0x06, 0x5c, 0xf2, 0xe3, 0x4b, 0x1d, - 0xb2, 0xd1, 0xd4, 0xd7, 0x98, 0xa1, 0xb7, 0xf3, 0xf4, 0x0c, 0xf9, 0xc3, 0x62, 0x2e, 0xfb, 0x83, - 0x17, 0xe6, 0x8f, 0xb9, 0x1c, 0xd4, 0x19, 0xab, 0x72, 0x5e, 0x1d, 0xfe, 0x17, 0x81, 0x2b, 0xb9, - 0x0c, 0x31, 0x4d, 0x1c, 0x56, 0x46, 0xa4, 0x29, 0x69, 0xf8, 0x89, 0xf3, 0x44, 0x87, 0xf2, 0x74, - 0x8e, 0xcd, 0xbf, 0x85, 0xc3, 0x23, 0x49, 0x98, 0x9e, 0x08, 0x49, 0xae, 0xab, 0xb0, 0x9c, 0x10, - 0x1c, 0x9c, 0x1f, 0x17, 0x83, 0x34, 0xe0, 0x41, 0xcb, 0xf4, 0xf1, 0x97, 0x3f, 0xe0, 0x08, 0x53, - 0x62, 0xc3, 0x52, 0xd6, 0xd3, 0xb8, 0x33, 0x4f, 0xbb, 0xc1, 0x44, 0x2c, 0x66, 0x62, 0x9a, 0x02, - 0xde, 0x18, 0x8e, 0xd8, 0x6f, 0x94, 0xf3, 0xaa, 0xf8, 0x1f, 0x04, 0x2e, 0x8f, 0x8e, 0x83, 0xda, - 0x3e, 0x87, 0x57, 0xb3, 0xda, 0xc6, 0x9f, 0x6d, 0x69, 0x75, 0xd9, 0x8c, 0x9e, 0x67, 0x8d, 0x57, - 0x80, 0xc6, 0x2d, 0xab, 0xb7, 0x1d, 0x6a, 0x34, 0x1f, 0xe1, 0xd4, 0x4b, 0x4e, 0x51, 0xcd, 0x3d, - 0x98, 0x8d, 0xb7, 0x22, 0x56, 0xc8, 0xc8, 0x6d, 0x57, 0x6d, 0x85, 0xe4, 0x11, 0x53, 0xff, 0x69, - 0x01, 0x5e, 0xd2, 0x5e, 0xe9, 0xef, 0x04, 0x16, 0x52, 0x7b, 0x8f, 0xb2, 0x3c, 0x3f, 0x39, 0x3b, - 0xb7, 0xbc, 0x3e, 0x3e, 0x20, 0xa6, 0x6e, 0x7e, 0xf2, 0xdd, 0xdf, 0xff, 0xff, 0x30, 0xfd, 0x01, - 0xdd, 0x64, 0x05, 0xff, 0x20, 0x58, 0x77, 0x57, 0x74, 0xac, 0xec, 0x5e, 0x3c, 0x8a, 0x0f, 0xbd, - 0xaf, 0xc2, 0xe8, 0x85, 0x3e, 0x25, 0xb0, 0x98, 0x59, 0x62, 0xb4, 0x36, 0x26, 0x9f, 0xfe, 0xf2, - 0x2d, 0xd7, 0x27, 0x81, 0xa0, 0x88, 0x35, 0x2d, 0xc2, 0xa4, 0x95, 0x22, 0x11, 0xf4, 0x4f, 0x02, - 0x73, 0xb8, 0x10, 0xe8, 0xcd, 0x53, 0x23, 0x65, 0x17, 0x5b, 0xf9, 0xd6, 0x78, 0xc6, 0x48, 0xe8, - 0x4b, 0x4d, 0xe8, 0x33, 0xfa, 0x28, 0x8f, 0x50, 0xb2, 0x81, 0x8a, 0xb3, 0xca, 0xba, 0xb8, 0x0a, - 0x8f, 0x58, 0x37, 0x42, 0x1c, 0x45, 0x5d, 0xb2, 0x3c, 0x34, 0xf7, 0xe8, 0x9d, 0x53, 0x09, 0xe6, - 0x6d, 0xb4, 0xf2, 0xbb, 0x93, 0xc2, 0x50, 0xe1, 0x5d, 0xad, 0xb0, 0x4e, 0xd7, 0xf3, 0x14, 0x8e, - 0x98, 0xe6, 0xac, 0x1b, 0x75, 0xc9, 0x6f, 0x04, 0xe8, 0xf0, 0x22, 0xa0, 0x13, 0x12, 0xe9, 0xf5, - 0xcb, 0x7b, 0x13, 0xe3, 0x50, 0xc1, 0x86, 0x56, 0x60, 0xd1, 0x5b, 0x13, 0x28, 0x50, 0xf4, 0x57, - 0x02, 0x8b, 0x99, 0x49, 0x54, 0xd0, 0xe3, 0xa3, 0x76, 0x44, 0x41, 0x8f, 0x8f, 0xdc, 0x06, 0xe6, - 0xa6, 0xa6, 0x7b, 0x8f, 0xbe, 0x5f, 0xd4, 0xe3, 0x71, 0x1f, 0xb1, 0xee, 0xd0, 0x16, 0x3a, 0xa2, - 0x3f, 0x13, 0xb8, 0x38, 0x30, 0x91, 0xe9, 0xed, 0xf1, 0xb9, 0xf4, 0x93, 0xbe, 0x31, 0x19, 0x08, - 0x25, 0xac, 0x6b, 0x09, 0x55, 0xba, 0x36, 0x9e, 0x04, 0xa1, 0xe8, 0x13, 0x02, 0xb3, 0xf1, 0xcc, - 0xa4, 0xd5, 0xd3, 0xeb, 0x9c, 0x1e, 0xd3, 0xe5, 0x9b, 0x63, 0xd9, 0x22, 0xab, 0x1b, 0x9a, 0x55, - 0x85, 0x1a, 0xec, 0xd4, 0x0f, 0x9e, 0xcd, 0xdd, 0x67, 0xc7, 0x06, 0x79, 0x7e, 0x6c, 0x90, 0xff, - 0x8e, 0x0d, 0xf2, 0xfd, 0x89, 0x31, 0xf5, 0xfc, 0xc4, 0x98, 0xfa, 0xe7, 0xc4, 0x98, 0x82, 0x55, - 0x57, 0xe6, 0x04, 0xdc, 0x26, 0x5f, 0x6c, 0x38, 0x6e, 0xd8, 0x3e, 0xd8, 0xb1, 0x9a, 0x72, 0x3f, - 0x15, 0xe0, 0x1d, 0x57, 0xa6, 0xc3, 0x7d, 0xdd, 0x0f, 0x18, 0x76, 0x7c, 0xa1, 0x76, 0x66, 0xf5, - 0xe7, 0xd5, 0xed, 0x17, 0x01, 0x00, 0x00, 0xff, 0xff, 0x55, 0x6b, 0x3d, 0x08, 0x74, 0x0e, 0x00, - 0x00, + // 1119 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xcf, 0x24, 0x6d, 0xfe, 0xbc, 0x34, 0x29, 0x99, 0x5a, 0xe0, 0x98, 0xb0, 0xb5, 0xa6, 0x7f, + 0x30, 0x6e, 0xd9, 0x8d, 0xdd, 0x14, 0x2a, 0xd4, 0x53, 0x0a, 0x84, 0x12, 0x09, 0x99, 0xad, 0x1a, + 0x24, 0x38, 0x58, 0x13, 0x7b, 0xb0, 0x57, 0x71, 0x76, 0xb6, 0x3b, 0x6b, 0x0b, 0x63, 0xe5, 0x00, + 0xe2, 0xd2, 0x1b, 0x12, 0x1f, 0x80, 0x7e, 0x02, 0x0e, 0x1c, 0x01, 0x09, 0xc1, 0xa9, 0x17, 0xa4, + 0x4a, 0x5c, 0xb8, 0x80, 0x50, 0xc2, 0x07, 0x41, 0x3b, 0x3b, 0x6b, 0xef, 0xda, 0xde, 0xac, 0xdd, + 0xe6, 0xe6, 0x9d, 0x79, 0xbf, 0x37, 0xbf, 0xdf, 0x7b, 0x6f, 0xde, 0x1b, 0x03, 0x71, 0x5c, 0xde, + 0x61, 0x36, 0xb5, 0x6b, 0xcc, 0x70, 0x59, 0xc3, 0x12, 0x9e, 0xdb, 0x35, 0x3a, 0x25, 0xe3, 0x51, + 0x9b, 0xb9, 0x5d, 0xdd, 0x71, 0xb9, 0xc7, 0xf1, 0xcb, 0x03, 0x1b, 0x3d, 0xb4, 0xd1, 0x3b, 0xa5, + 0x5c, 0xb1, 0xc6, 0xc5, 0x21, 0x17, 0xc6, 0x3e, 0x15, 0x2c, 0x00, 0x18, 0x9d, 0xd2, 0x3e, 0xf3, + 0x68, 0xc9, 0x70, 0x68, 0xc3, 0xb2, 0xa9, 0x67, 0x71, 0x3b, 0xf0, 0x91, 0xcb, 0x34, 0x78, 0x83, + 0xcb, 0x9f, 0x86, 0xff, 0x4b, 0xad, 0x6e, 0x34, 0x38, 0x6f, 0xb4, 0x98, 0x41, 0x1d, 0xcb, 0xa0, + 0xb6, 0xcd, 0x3d, 0x09, 0x11, 0x6a, 0xf7, 0x5a, 0x02, 0xb7, 0x3e, 0x87, 0xc0, 0xac, 0x98, 0x60, + 0x46, 0xdb, 0x5e, 0x93, 0xbb, 0xd6, 0x97, 0x51, 0x1a, 0x57, 0x12, 0x6c, 0x1d, 0xea, 0xd2, 0x43, + 0x75, 0x2e, 0xa9, 0xc0, 0x2b, 0x1f, 0xfb, 0x6a, 0x76, 0x98, 0x67, 0x2a, 0x1b, 0x93, 0x3d, 0x6a, + 0x33, 0xe1, 0xe1, 0xdb, 0x30, 0x77, 0xc0, 0xba, 0x59, 0x94, 0x47, 0x85, 0xe5, 0xf2, 0x15, 0x7d, + 0x7c, 0x60, 0xf4, 0x10, 0xb5, 0xcb, 0xba, 0xa6, 0x6f, 0x4f, 0x6a, 0x90, 0x1d, 0xf5, 0x28, 0x1c, + 0x6e, 0x0b, 0x86, 0x77, 0x60, 0x31, 0xc4, 0x2a, 0xbf, 0xd7, 0xd2, 0xfc, 0xbe, 0x67, 0x7b, 0x6e, + 0x77, 0xfb, 0xdc, 0xd3, 0x7f, 0x2e, 0xcf, 0x98, 0x7d, 0x30, 0x79, 0x8c, 0x60, 0x7d, 0xe8, 0x14, + 0x8b, 0x89, 0x90, 0xf9, 0x55, 0x58, 0xa5, 0x42, 0x30, 0xaf, 0x5a, 0x6b, 0x51, 0x21, 0xaa, 0x56, + 0x5d, 0x1e, 0xb6, 0x64, 0x5e, 0x90, 0xab, 0xf7, 0xfc, 0xc5, 0xfb, 0x75, 0xfc, 0x3e, 0xc0, 0x20, + 0x75, 0xd9, 0x9a, 0xa4, 0x73, 0x5d, 0x0f, 0xf2, 0xac, 0xfb, 0x79, 0xd6, 0x83, 0xc2, 0x50, 0x79, + 0xd6, 0x2b, 0xb4, 0xc1, 0xd4, 0x09, 0x66, 0x04, 0x49, 0x7e, 0x44, 0x90, 0x1b, 0xc7, 0x45, 0x69, + 0xde, 0x05, 0x70, 0xfb, 0xab, 0x59, 0x94, 0x9f, 0x9b, 0x56, 0x75, 0x04, 0x8e, 0x77, 0xc6, 0x70, + 0x7e, 0x3d, 0x95, 0x73, 0xc0, 0x24, 0x46, 0xfa, 0x09, 0x82, 0x4b, 0x92, 0xf4, 0x07, 0x54, 0x98, + 0xbc, 0xc5, 0x5e, 0x2c, 0xe9, 0x38, 0x0b, 0x0b, 0xb4, 0x5e, 0x77, 0x99, 0x10, 0xd9, 0x59, 0x19, + 0xea, 0xf0, 0x13, 0xdf, 0x81, 0x73, 0x2e, 0x6f, 0xb1, 0xec, 0x5c, 0x1e, 0x15, 0x56, 0xcb, 0x57, + 0xd3, 0x3c, 0x4a, 0x2e, 0x12, 0x41, 0x4a, 0x90, 0x89, 0x33, 0x54, 0x01, 0x5d, 0x87, 0xc5, 0x26, + 0x15, 0x55, 0xe9, 0xd5, 0xe7, 0xb9, 0x68, 0x2e, 0x34, 0x03, 0x13, 0x62, 0xc0, 0x6b, 0x12, 0x52, + 0x61, 0x76, 0xdd, 0xb2, 0x1b, 0xfe, 0xda, 0xbd, 0x26, 0xb5, 0xfb, 0x79, 0xc3, 0xab, 0x30, 0xdb, + 0xaf, 0x86, 0x59, 0xab, 0x4e, 0xbe, 0x42, 0xa0, 0x25, 0x21, 0xd4, 0x71, 0x55, 0xb8, 0xe4, 0x04, + 0x9b, 0xf2, 0xc8, 0x6a, 0x4d, 0x6e, 0xab, 0x08, 0xbd, 0x91, 0xa4, 0x67, 0xc4, 0x9f, 0x4a, 0xe6, + 0x9a, 0x33, 0xbc, 0x41, 0xbe, 0x4f, 0xe4, 0x20, 0x5e, 0x30, 0x2b, 0x67, 0x55, 0xe1, 0x7f, 0x20, + 0xb8, 0x9c, 0xc8, 0x50, 0x85, 0x89, 0x42, 0x66, 0x4c, 0x98, 0xc2, 0x82, 0x9f, 0x3a, 0x4e, 0x78, + 0x24, 0x4e, 0x67, 0x58, 0xfc, 0x3b, 0xaa, 0x79, 0x84, 0x01, 0x93, 0x1d, 0x21, 0x8c, 0x75, 0x11, + 0xd6, 0x42, 0x82, 0xc3, 0xfd, 0xe3, 0xa2, 0x1b, 0x05, 0xdc, 0xaf, 0x13, 0x47, 0xdd, 0xfc, 0x21, + 0x47, 0x2a, 0x24, 0x26, 0xac, 0xc6, 0x3d, 0x4d, 0xda, 0xf3, 0xa4, 0x1b, 0x15, 0x88, 0x95, 0xd8, + 0x99, 0x84, 0xc1, 0xab, 0xa3, 0x27, 0x0e, 0x0a, 0xe5, 0xac, 0x32, 0xfe, 0x2b, 0x82, 0x8d, 0xf1, + 0xe7, 0x28, 0x6d, 0x7b, 0xf0, 0x52, 0x5c, 0xdb, 0xe4, 0xbd, 0x2d, 0xaa, 0x2e, 0x1e, 0xd1, 0xb3, + 0xcc, 0x71, 0x06, 0x70, 0x50, 0xb2, 0x72, 0xda, 0x29, 0x8d, 0xe4, 0x81, 0xea, 0x7a, 0xe1, 0xaa, + 0x52, 0x73, 0x17, 0xe6, 0x83, 0xa9, 0xa8, 0x32, 0xa4, 0x25, 0x96, 0xab, 0xb4, 0x52, 0xe4, 0x15, + 0x86, 0xfc, 0x1e, 0x5e, 0xe0, 0x3d, 0xda, 0xb2, 0xea, 0xd4, 0x63, 0xa3, 0x7d, 0xe7, 0x39, 0x2f, + 0xf0, 0x2e, 0x5c, 0x90, 0x97, 0xa9, 0xed, 0xf8, 0x6e, 0xfd, 0xde, 0xea, 0x47, 0x98, 0x24, 0xe2, + 0x79, 0x8b, 0x3d, 0x94, 0xa6, 0x8a, 0xe1, 0xb2, 0xdb, 0x5f, 0x11, 0x78, 0x03, 0x96, 0xa8, 0x23, + 0x91, 0xae, 0xc8, 0xce, 0xe5, 0xe7, 0x0a, 0x4b, 0xe6, 0x60, 0x81, 0x7c, 0xa2, 0xae, 0xf8, 0x38, + 0x0d, 0x2a, 0x4a, 0x19, 0x38, 0xcf, 0x5c, 0x97, 0xbb, 0xea, 0x36, 0x04, 0x1f, 0x58, 0x03, 0x08, + 0x5f, 0x1f, 0xac, 0x2e, 0xbb, 0xff, 0xa2, 0x19, 0x59, 0x29, 0x7f, 0xb3, 0x02, 0xe7, 0xa5, 0x67, + 0xfc, 0x0b, 0x82, 0xe5, 0xc8, 0xab, 0x00, 0x1b, 0x49, 0x3a, 0x12, 0x5e, 0x24, 0xb9, 0xcd, 0xc9, + 0x01, 0x01, 0x65, 0xf2, 0xe1, 0xd7, 0x7f, 0xfe, 0xf7, 0xdd, 0xec, 0xbb, 0x78, 0xdb, 0x48, 0x79, + 0x5f, 0x19, 0xbd, 0x03, 0xd6, 0xd5, 0xe3, 0xaf, 0x86, 0xa3, 0x60, 0xd1, 0xfe, 0xdc, 0xf3, 0x3f, + 0xf0, 0x13, 0x04, 0x2b, 0xb1, 0x11, 0x8f, 0x4b, 0x13, 0xf2, 0x19, 0x3c, 0x4d, 0x72, 0xe5, 0x69, + 0x20, 0x4a, 0x44, 0x41, 0x8a, 0x20, 0x38, 0x9f, 0x26, 0x02, 0xff, 0x86, 0x60, 0x41, 0x8d, 0x4b, + 0x7c, 0xe3, 0xd4, 0x93, 0xe2, 0x63, 0x3f, 0x77, 0x73, 0x32, 0x63, 0x45, 0xe8, 0x33, 0x49, 0xe8, + 0x21, 0x7e, 0x90, 0x44, 0x28, 0x9c, 0xcf, 0xe9, 0x51, 0x35, 0x7a, 0xea, 0xa1, 0x70, 0x64, 0xf4, + 0x7c, 0xc4, 0x91, 0x5f, 0x25, 0x6b, 0x23, 0x53, 0x01, 0xdf, 0x3e, 0x95, 0x60, 0xd2, 0xbc, 0xcf, + 0xbd, 0x35, 0x2d, 0x4c, 0x29, 0xbc, 0x23, 0x15, 0x96, 0xf1, 0x66, 0x92, 0xc2, 0x31, 0xb3, 0xce, + 0xe8, 0xf9, 0x55, 0xf2, 0x33, 0x02, 0x3c, 0x3a, 0x26, 0xf1, 0x94, 0x44, 0xfa, 0xf5, 0xf2, 0xf6, + 0xd4, 0x38, 0xa5, 0x60, 0x4b, 0x2a, 0xd0, 0xf1, 0xcd, 0x29, 0x14, 0x08, 0xfc, 0x13, 0x82, 0x95, + 0x58, 0x9f, 0x4e, 0xa9, 0xf1, 0x71, 0x13, 0x34, 0xa5, 0xc6, 0xc7, 0xce, 0x4a, 0xb2, 0x2d, 0xe9, + 0xde, 0xc5, 0xef, 0xa4, 0xd5, 0x78, 0x50, 0x47, 0x46, 0x6f, 0x64, 0x46, 0x1f, 0xe1, 0x1f, 0x10, + 0x5c, 0x1c, 0x9a, 0x57, 0xf8, 0xd6, 0xe4, 0x5c, 0x06, 0x41, 0xdf, 0x9a, 0x0e, 0xa4, 0x24, 0x6c, + 0x4a, 0x09, 0x45, 0x5c, 0x98, 0x4c, 0x02, 0x13, 0xf8, 0x31, 0x82, 0xf9, 0x60, 0xa2, 0xe0, 0xe2, + 0xe9, 0x79, 0x8e, 0x0e, 0xb1, 0xdc, 0x8d, 0x89, 0x6c, 0x15, 0xab, 0xeb, 0x92, 0x55, 0x1e, 0x6b, + 0xc6, 0xa9, 0x7f, 0x07, 0xf1, 0xdf, 0x08, 0xf0, 0x68, 0xef, 0x4f, 0xa9, 0xdb, 0xc4, 0x81, 0x97, + 0x52, 0xb7, 0xc9, 0x43, 0x86, 0xec, 0x49, 0xbe, 0x15, 0xfc, 0x51, 0x12, 0xdf, 0x8e, 0xc2, 0xc6, + 0xaf, 0x5e, 0x5a, 0x9f, 0xd9, 0x3e, 0x78, 0x7a, 0xac, 0xa1, 0x67, 0xc7, 0x1a, 0xfa, 0xf7, 0x58, + 0x43, 0xdf, 0x9e, 0x68, 0x33, 0xcf, 0x4e, 0xb4, 0x99, 0xbf, 0x4e, 0xb4, 0x19, 0x58, 0xb7, 0x78, + 0x02, 0xd9, 0x0a, 0xfa, 0x74, 0xab, 0x61, 0x79, 0xcd, 0xf6, 0xbe, 0x5e, 0xe3, 0x87, 0x11, 0x42, + 0x6f, 0x5a, 0x3c, 0x4a, 0xef, 0x8b, 0x01, 0x41, 0xaf, 0xeb, 0x30, 0xb1, 0x3f, 0x2f, 0xff, 0x5c, + 0xdf, 0xfa, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x53, 0x45, 0x14, 0x20, 0x72, 0x10, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -957,6 +1087,10 @@ type QueryClient interface { // Params returns the registry module parameters, including the default role authorization // policies. Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error) + // ValidateRoleChange performs a read-only dry-run of a role-change batch, reporting whether the + // supplied approvers would satisfy every affected role's authorization policy without writing + // any state. + ValidateRoleChange(ctx context.Context, in *QueryValidateRoleChangeRequest, opts ...grpc.CallOption) (*QueryValidateRoleChangeResponse, error) } type queryClient struct { @@ -1039,6 +1173,15 @@ func (c *queryClient) Params(ctx context.Context, in *QueryParamsRequest, opts . return out, nil } +func (c *queryClient) ValidateRoleChange(ctx context.Context, in *QueryValidateRoleChangeRequest, opts ...grpc.CallOption) (*QueryValidateRoleChangeResponse, error) { + out := new(QueryValidateRoleChangeResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Query/ValidateRoleChange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // QueryServer is the server API for Query service. type QueryServer interface { // GetRegistry returns the registry entry for a given key. @@ -1060,6 +1203,10 @@ type QueryServer interface { // Params returns the registry module parameters, including the default role authorization // policies. Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error) + // ValidateRoleChange performs a read-only dry-run of a role-change batch, reporting whether the + // supplied approvers would satisfy every affected role's authorization policy without writing + // any state. + ValidateRoleChange(context.Context, *QueryValidateRoleChangeRequest) (*QueryValidateRoleChangeResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -1090,6 +1237,9 @@ func (*UnimplementedQueryServer) RegistryClasses(ctx context.Context, req *Query func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsRequest) (*QueryParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") } +func (*UnimplementedQueryServer) ValidateRoleChange(ctx context.Context, req *QueryValidateRoleChangeRequest) (*QueryValidateRoleChangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ValidateRoleChange not implemented") +} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -1239,6 +1389,24 @@ func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interf return interceptor(ctx, in, info, handler) } +func _Query_ValidateRoleChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryValidateRoleChangeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).ValidateRoleChange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Query/ValidateRoleChange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).ValidateRoleChange(ctx, req.(*QueryValidateRoleChangeRequest)) + } + return interceptor(ctx, in, info, handler) +} + var Query_serviceDesc = _Query_serviceDesc var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "provenance.registry.v1.Query", @@ -1276,6 +1444,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Params", Handler: _Query_Params_Handler, }, + { + MethodName: "ValidateRoleChange", + Handler: _Query_ValidateRoleChange_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "provenance/registry/v1/query.proto", @@ -1894,6 +2066,104 @@ func (m *QueryParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *QueryValidateRoleChangeRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryValidateRoleChangeRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryValidateRoleChangeRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Approvers) > 0 { + for iNdEx := len(m.Approvers) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Approvers[iNdEx]) + copy(dAtA[i:], m.Approvers[iNdEx]) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Approvers[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } + if len(m.RoleUpdates) > 0 { + for iNdEx := len(m.RoleUpdates) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RoleUpdates[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if m.Key != nil { + { + size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryValidateRoleChangeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *QueryValidateRoleChangeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryValidateRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Authorized { + i-- + if m.Authorized { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if len(m.Error) > 0 { + i -= len(m.Error) + copy(dAtA[i:], m.Error) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Error))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -2133,6 +2403,47 @@ func (m *QueryParamsResponse) Size() (n int) { return n } +func (m *QueryValidateRoleChangeRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovQuery(uint64(l)) + } + if len(m.RoleUpdates) > 0 { + for _, e := range m.RoleUpdates { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if len(m.Approvers) > 0 { + for _, s := range m.Approvers { + l = len(s) + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func (m *QueryValidateRoleChangeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Error) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Authorized { + n += 2 + } + return n +} + func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -3664,6 +3975,260 @@ func (m *QueryParamsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *QueryValidateRoleChangeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryValidateRoleChangeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryValidateRoleChangeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Key == nil { + m.Key = &RegistryKey{} + } + if err := m.Key.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RoleUpdates", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RoleUpdates = append(m.RoleUpdates, RoleUpdate{}) + if err := m.RoleUpdates[len(m.RoleUpdates)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Approvers", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Approvers = append(m.Approvers, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryValidateRoleChangeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: QueryValidateRoleChangeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryValidateRoleChangeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Error", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Error = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Authorized", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Authorized = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/query.pb.gw.go b/x/registry/types/query.pb.gw.go index ff3737e139..d510c09c8a 100644 --- a/x/registry/types/query.pb.gw.go +++ b/x/registry/types/query.pb.gw.go @@ -505,6 +505,100 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } +var ( + filter_Query_ValidateRoleChange_0 = &utilities.DoubleArray{Encoding: map[string]int{"key": 0, "asset_class_id": 1, "nft_id": 2}, Base: []int{1, 1, 1, 2, 0, 0}, Check: []int{0, 1, 2, 2, 3, 4}} +) + +func request_Query_ValidateRoleChange_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryValidateRoleChangeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["key.asset_class_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.asset_class_id") + } + + err = runtime.PopulateFieldFromPath(&protoReq, "key.asset_class_id", val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.asset_class_id", err) + } + + val, ok = pathParams["key.nft_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.nft_id") + } + + err = runtime.PopulateFieldFromPath(&protoReq, "key.nft_id", val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.nft_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ValidateRoleChange_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.ValidateRoleChange(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_ValidateRoleChange_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryValidateRoleChangeRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["key.asset_class_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.asset_class_id") + } + + err = runtime.PopulateFieldFromPath(&protoReq, "key.asset_class_id", val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.asset_class_id", err) + } + + val, ok = pathParams["key.nft_id"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.nft_id") + } + + err = runtime.PopulateFieldFromPath(&protoReq, "key.nft_id", val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.nft_id", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ValidateRoleChange_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.ValidateRoleChange(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -695,6 +789,29 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) + mux.Handle("GET", pattern_Query_ValidateRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_ValidateRoleChange_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ValidateRoleChange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -896,6 +1013,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) + mux.Handle("GET", pattern_Query_ValidateRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_ValidateRoleChange_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_ValidateRoleChange_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -915,6 +1052,8 @@ var ( pattern_Query_RegistryClasses_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "registry_classes"}, "", runtime.AssumeColonVerbOpt(false))) pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_ValidateRoleChange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"provenance", "registry", "v1", "validate_role_change", "key.asset_class_id", "key.nft_id"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -933,4 +1072,6 @@ var ( forward_Query_RegistryClasses_0 = runtime.ForwardResponseMessage forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_ValidateRoleChange_0 = runtime.ForwardResponseMessage ) From 1dfcc1c19e6d609291729be413ef78aee7bafc30 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 10:06:02 -0600 Subject: [PATCH 19/36] registry: address PR #4 review feedback - class.go: roleAuthorizationsForEntry now fails fast (panic) on a store/read error resolving a registry class, instead of silently downgrading to the weaker params/legacy authorization tier. - genesis.go: GenesisState.Validate now verifies that every entry's registry_class_id references a registry class present in genesis, preventing an invalid state that would change authorization behavior at runtime. - msg_server.go SetRoles: authorize and resolve event signers against the additions (additions(current, desired)) rather than the full desired set, so ASSIGNMENT_NEW resolves only newly-added addresses (matches the pending-apply path) and no longer errors when the desired set contains current+new members. - cli/tx.go CmdUpdateParams: submit MsgUpdateParams wrapped in a governance proposal via provcli.GenerateOrBroadcastTxCLIAsGovProp + gov prop flags, matching the documented governance-only workflow used by other modules. Adds regression tests: genesis unknown/known class-id references and a SetRoles ASSIGNMENT_NEW additions scenario. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/client/cli/tx.go | 34 ++++------ x/registry/keeper/class.go | 7 ++- x/registry/keeper/msg_server.go | 9 ++- .../role_event_query_acceptance_test.go | 63 +++++++++++++++++++ x/registry/types/genesis.go | 17 +++-- x/registry/types/genesis_test.go | 40 +++++++++++- 6 files changed, 137 insertions(+), 33 deletions(-) diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index b75407db3e..edf87d9f86 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -11,9 +11,9 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli" + "github.com/provenance-io/provenance/internal/provcli" "github.com/provenance-io/provenance/x/registry/types" ) @@ -395,21 +395,19 @@ role_authorizations field is used.`, } // CmdUpdateParams returns the command to update the registry module params via a governance -// proposal. The params are read from a proto-JSON Params file. This message must be submitted -// through governance; the authority defaults to the governance module account address and may be -// overridden with --authority. +// proposal. The params are read from a proto-JSON Params file and wrapped in a gov proposal, since +// MsgUpdateParams is authorized by the governance module account. func CmdUpdateParams() *cobra.Command { cmd := &cobra.Command{ Use: "update-params ", - Short: "Update the registry module params from a JSON file (governance)", - Long: `Update the registry module params from a proto-JSON Params file, e.g.: + Short: "Submit a governance proposal to update the registry module params from a JSON file", + Long: `Submit a governance proposal to update the registry module params from a proto-JSON Params file, e.g.: { "role_authorizations": [ { "role": "REGISTRY_ROLE_CONTROLLER", "authorizations": [ ... ] } ] } -This message must be authorized by governance. The authority defaults to the governance module -account address; use --authority to override. Submit the generated message through -"tx gov submit-proposal".`, +The message is wrapped in a governance proposal and must be passed by governance. The authority +defaults to the governance module account address; use --authority to override.`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { clientCtx, err := client.GetClientTxContext(cmd) @@ -427,24 +425,18 @@ account address; use --authority to override. Submit the generated message throu return fmt.Errorf("failed to unmarshal params JSON: %w", err) } - authority, err := cmd.Flags().GetString("authority") - if err != nil { - return err - } - if authority == "" { - authority = authtypes.NewModuleAddress(govtypes.ModuleName).String() - } - + flagSet := cmd.Flags() msg := types.MsgUpdateParams{ - Authority: authority, + Authority: provcli.GetAuthority(flagSet), Params: params, } - return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + return provcli.GenerateOrBroadcastTxCLIAsGovProp(clientCtx, flagSet, &msg) }, } - cmd.Flags().String("authority", "", "the authority address (defaults to the gov module account)") + govcli.AddGovPropFlagsToCmd(cmd) + provcli.AddAuthorityFlagToCmd(cmd) flags.AddTxFlagsToCmd(cmd) return cmd } diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go index 684acfc95e..e7fbe2842c 100644 --- a/x/registry/keeper/class.go +++ b/x/registry/keeper/class.go @@ -114,7 +114,12 @@ func (k Keeper) GetRegistryClasses(ctx context.Context, pagination *query.PageRe func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.RegistryEntry) map[types.RegistryRole]types.RoleAuthorization { if entry != nil && entry.RegistryClassId != "" { class, err := k.GetRegistryClass(ctx, entry.RegistryClassId) - if err == nil && class != nil { + if err != nil { + // A store/read error must not silently downgrade to a weaker fallback policy. Fail fast + // so the authorization decision is never made against the wrong tier. + panic(fmt.Errorf("could not resolve registry class %q for authorization: %w", entry.RegistryClassId, err)) + } + if class != nil { return types.RoleAuthorizationMapFrom(class.RoleAuthorizations) } } diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 55eb5e5792..9174afc6a8 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -146,9 +146,11 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types roleAuths := k.roleAuthorizationsForEntry(ctx, entry) for _, update := range msg.RoleUpdates { if roleAuth, ok := roleAuths[update.Role]; ok { - // The new addresses for this role are the desired state from the update. A single signer + // ASSIGNMENT_NEW resolves only the newly-added addresses, so authorize against the + // additions relative to the current state (not the full desired set). A single signer // must satisfy the policy on its own; multi-party changes go through ProposeRoleChange. - if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, update.Addresses, []string{msg.Signer}); err != nil { + newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, newAddrs, []string{msg.Signer}); err != nil { return nil, err } } else { @@ -164,7 +166,8 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types } for _, update := range msg.RoleUpdates { - signers := k.roleChangeSigners(ctx, entry, update.Role, update.Addresses, []string{msg.Signer}) + newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) + signers := k.roleChangeSigners(ctx, entry, update.Role, newAddrs, []string{msg.Signer}) k.emitRoleUpdated(ctx, entry, update.Role, signers) } diff --git a/x/registry/keeper/role_event_query_acceptance_test.go b/x/registry/keeper/role_event_query_acceptance_test.go index bf40689a1e..356a2bc0f5 100644 --- a/x/registry/keeper/role_event_query_acceptance_test.go +++ b/x/registry/keeper/role_event_query_acceptance_test.go @@ -222,6 +222,69 @@ func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_RegistryClas s.Require().Equal([]string{s.currentController}, ev.Signers[0].Addresses) } +// TestSetRoles_AdditionsOnlyForNewAssignment: MsgSetRoles takes the full desired address set, but +// the authorization engine's ASSIGNMENT_NEW must resolve only the newly-added addresses. A policy +// that requires the incoming (NEW) member to sign must be satisfiable single-signer by that member +// even when the desired set also contains already-current members. +func (s *RoleEventAndQueryAcceptanceTestSuite) TestSetRoles_AdditionsOnlyForNewAssignment() { + // Policy: a SERVICER may be added if the newly-assigned servicer signs (SERVICER@NEW). + selfAddServicer := types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "a new servicer may add themselves", + Signatures: []*types.SignatureRequirement{{ + Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + Roles: []*types.RoleAssignment{{ + RoleSelector: &types.RoleAssignment_RegistryRole{ + RegistryRole: types.RegistryRole_REGISTRY_ROLE_SERVICER, + }, + Assignment: types.Assignment_ASSIGNMENT_NEW, + }}, + }}, + }}, + } + maintainer := genAddr() + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: maintainer, + RegistryClassId: "servicer-class", + AssetClassId: s.nftClass.Id, + Maintainer: maintainer, + RoleAuthorizations: []types.RoleAuthorization{selfAddServicer}, + }) + s.Require().NoError(err) + + existingServicer := genAddr() + newServicer := genAddr() + key := s.mintNFT("evt-additions") + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{existingServicer}}, + }, "servicer-class")) + + s.resetEvents() + // Desired state is [existing, new]; only the addition (newServicer) needs to sign. Before the + // additions fix this errored because ASSIGNMENT_NEW resolved to both addresses. + _, err = s.msgServer.SetRoles(s.ctx, &types.MsgSetRoles{ + Signer: newServicer, + Key: key, + RoleUpdates: []types.RoleUpdate{{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{existingServicer, newServicer}, + }}, + }) + s.Require().NoError(err) + + evts := s.roleUpdatedEvents() + s.Require().Len(evts, 1) + ev := evts[0] + s.Require().Equal("SERVICER", ev.Role) + s.Require().Equal([]string{existingServicer, newServicer}, ev.Addresses) + s.Require().Equal([]string{existingServicer}, ev.PreviousAddresses) + s.Require().Len(ev.Signers, 1) + s.Require().Equal("REGISTRY_ROLE_SERVICER", ev.Signers[0].Role) + s.Require().Equal("ASSIGNMENT_NEW", ev.Signers[0].Assignment) + s.Require().Equal([]string{newServicer}, ev.Signers[0].Addresses) +} + // --- Phase C5: ValidateRoleChange query --------------------------------------------------------- func (s *RoleEventAndQueryAcceptanceTestSuite) controllerUpdate() []types.RoleUpdate { diff --git a/x/registry/types/genesis.go b/x/registry/types/genesis.go index f49b01e2a2..4ec4d68f93 100644 --- a/x/registry/types/genesis.go +++ b/x/registry/types/genesis.go @@ -11,12 +11,6 @@ func DefaultGenesis() *GenesisState { // Validate validates the GenesisState. func (m *GenesisState) Validate() error { - for _, entry := range m.Entries { - if err := entry.Validate(); err != nil { - return fmt.Errorf("entry: %w", err) - } - } - seenClasses := make(map[string]bool, len(m.RegistryClasses)) for _, class := range m.RegistryClasses { if err := class.Validate(); err != nil { @@ -28,6 +22,17 @@ func (m *GenesisState) Validate() error { seenClasses[class.RegistryClassId] = true } + for _, entry := range m.Entries { + if err := entry.Validate(); err != nil { + return fmt.Errorf("entry: %w", err) + } + // An entry that references a registry class must reference one that exists in genesis; + // otherwise its authorization tier would silently fall back to params/legacy at runtime. + if entry.RegistryClassId != "" && !seenClasses[entry.RegistryClassId] { + return fmt.Errorf("entry %q references unknown registry class id: %q", entry.Key.String(), entry.RegistryClassId) + } + } + if err := m.Params.Validate(); err != nil { return fmt.Errorf("params: %w", err) } diff --git a/x/registry/types/genesis_test.go b/x/registry/types/genesis_test.go index 095d9feb62..5c05221e7e 100644 --- a/x/registry/types/genesis_test.go +++ b/x/registry/types/genesis_test.go @@ -321,18 +321,26 @@ func TestGenesisState_Validate(t *testing.T) { expErr: "", }, { - name: "valid entry - multiple addresses", + name: "valid entry - references existing registry class", genesis: &types.GenesisState{ + RegistryClasses: []types.RegistryClass{ + { + RegistryClassId: "class-a", + AssetClassId: "class1", + Maintainer: validAddr, + }, + }, Entries: []types.RegistryEntry{ { Key: &types.RegistryKey{ AssetClassId: "class1", NftId: "nft1", }, + RegistryClassId: "class-a", Roles: []types.RolesEntry{ { Role: types.RegistryRole_REGISTRY_ROLE_ORIGINATOR, - Addresses: []string{validAddr, "cosmos1w6t0l7z0yerj49ehnqwqaayxqpe3u7e23edgma"}, + Addresses: []string{validAddr}, }, }, }, @@ -340,6 +348,34 @@ func TestGenesisState_Validate(t *testing.T) { }, expErr: "", }, + { + name: "invalid entry - references unknown registry class", + genesis: &types.GenesisState{ + RegistryClasses: []types.RegistryClass{ + { + RegistryClassId: "class-a", + AssetClassId: "class1", + Maintainer: validAddr, + }, + }, + Entries: []types.RegistryEntry{ + { + Key: &types.RegistryKey{ + AssetClassId: "class1", + NftId: "nft1", + }, + RegistryClassId: "class-missing", + Roles: []types.RolesEntry{ + { + Role: types.RegistryRole_REGISTRY_ROLE_ORIGINATOR, + Addresses: []string{validAddr}, + }, + }, + }, + }, + }, + expErr: "references unknown registry class id", + }, } for _, tc := range tests { From 07484a30a893940209e21b2b7753b97ba51fa197 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 10:48:58 -0600 Subject: [PATCH 20/36] registry: expand test coverage for class validation, query RPCs, and policy engine Add unit tests for RegistryClass/authorization config validation (every rejection path), the RegistryClass/RegistryClasses/Params query RPCs, keeper class store accessors, the policy engine's fail-closed branches, and revoke EventRoleUpdated emission. Raises keeper coverage 71% -> 79%. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../keeper/authorization_engine_test.go | 173 ++++++++++ x/registry/keeper/query_server_test.go | 171 +++++++++ .../role_event_query_acceptance_test.go | 31 ++ x/registry/types/authorization_test.go | 151 ++++++++ x/registry/types/class_test.go | 326 ++++++++++++++++++ 5 files changed, 852 insertions(+) create mode 100644 x/registry/keeper/authorization_engine_test.go create mode 100644 x/registry/types/authorization_test.go create mode 100644 x/registry/types/class_test.go diff --git a/x/registry/keeper/authorization_engine_test.go b/x/registry/keeper/authorization_engine_test.go new file mode 100644 index 0000000000..590c030aaf --- /dev/null +++ b/x/registry/keeper/authorization_engine_test.go @@ -0,0 +1,173 @@ +package keeper_test + +import ( + "github.com/provenance-io/provenance/x/registry/types" +) + +// These tests exercise the policy engine's fail-closed branches directly via +// ValidateRoleChangeAuthorization. A misconfigured policy (unspecified enums, singular assignments +// resolving to more than one address, selectors used in unsupported ways) must surface an error and +// must NEVER be silently treated as satisfied. + +// engineEntry builds an in-memory registry entry (owned by s.user1) seeded with the given roles. +func (s *KeeperTestSuite) engineEntry(roles []types.RolesEntry) *types.RegistryEntry { + return &types.RegistryEntry{ + Key: &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id}, + Roles: roles, + } +} + +// singleSigPolicy wraps one role selector in a REQUIRED_ALL single-path policy for the SERVICER role. +func singleSigPolicy(ra *types.RoleAssignment) types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "engine fail-closed path", + Signatures: []*types.SignatureRequirement{{ + Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + Roles: []*types.RoleAssignment{ra}, + }}, + }}, + } +} + +func (s *KeeperTestSuite) TestEngine_AssignmentCurrentResolvesMultiple() { + // CONTROLLER currently held by two addresses; singular ASSIGNMENT_CURRENT must reject. + entry := s.engineEntry([]types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1, s.user2}}, + }) + policy := singleSigPolicy(&types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }) + + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, []string{s.user1, s.user2}) + s.Require().Error(err) + s.Require().Contains(err.Error(), "ASSIGNMENT_CURRENT for role CONTROLLER resolved to 2 addresses, expected exactly one") +} + +func (s *KeeperTestSuite) TestEngine_AssignmentNewResolvesMultiple() { + entry := s.engineEntry(nil) + policy := singleSigPolicy(&types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_SERVICER}, + Assignment: types.Assignment_ASSIGNMENT_NEW, + }) + + newAddrs := []string{s.user1, s.user2} + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, newAddrs, newAddrs) + s.Require().Error(err) + s.Require().Contains(err.Error(), "ASSIGNMENT_NEW for role SERVICER resolved to 2 addresses, expected exactly one") +} + +func (s *KeeperTestSuite) TestEngine_UnspecifiedAssignment() { + entry := s.engineEntry([]types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }) + policy := singleSigPolicy(&types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER}, + Assignment: types.Assignment_ASSIGNMENT_UNSPECIFIED, + }) + + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, []string{s.user1}) + s.Require().Error(err) + s.Require().Contains(err.Error(), "unsupported assignment") +} + +func (s *KeeperTestSuite) TestEngine_NftRoleWithNonCurrentAssignment() { + entry := s.engineEntry(nil) + policy := singleSigPolicy(&types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_NftRole{NftRole: types.NftRole_NFT_ROLE_NFT_OWNER}, + Assignment: types.Assignment_ASSIGNMENT_NEW, + }) + + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, []string{s.user1}) + s.Require().Error(err) + s.Require().Contains(err.Error(), "NftRole may only be used with ASSIGNMENT_CURRENT* variants") +} + +func (s *KeeperTestSuite) TestEngine_UnsupportedNftRole() { + entry := s.engineEntry(nil) + policy := singleSigPolicy(&types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_NftRole{NftRole: types.NftRole_NFT_ROLE_UNSPECIFIED}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }) + + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, []string{s.user1}) + s.Require().Error(err) + s.Require().Contains(err.Error(), "unsupported NftRole") +} + +func (s *KeeperTestSuite) TestEngine_NoRoleSelectorSet() { + entry := s.engineEntry(nil) + policy := singleSigPolicy(&types.RoleAssignment{ + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }) + + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, []string{s.user1}) + s.Require().Error(err) + s.Require().Contains(err.Error(), "role assignment has no role selector set") +} + +func (s *KeeperTestSuite) TestEngine_UnsupportedSignatureType() { + entry := s.engineEntry([]types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }) + policy := types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Description: "bad signature type", + Signatures: []*types.SignatureRequirement{{ + Type: types.SignatureType_SIGNATURE_TYPE_UNSPECIFIED, + Roles: []*types.RoleAssignment{{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }}, + }}, + }}, + } + + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, []string{s.user1}) + s.Require().Error(err) + s.Require().Contains(err.Error(), "unsupported signature requirement type") +} + +// TestEngine_RequiredAnyIfSet_NoRolesSet confirms REQUIRED_ANY_IF_SET passes (vacuously) when none +// of its roles resolve to addresses, while REQUIRED_ANY rejects in the same situation. This is the +// empty-role distinction between the two ANY signature types. +func (s *KeeperTestSuite) TestEngine_RequiredAnyIfSet_NoRolesSet() { + // Entry has no SECURED_PARTY_FOR_ENOTE set, so the selector resolves empty. + entry := s.engineEntry(nil) + selector := &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT_ANY, + } + + s.Run("required_any_if_set passes when no role resolves", func() { + policy := types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Signatures: []*types.SignatureRequirement{{ + Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY_IF_SET, + Roles: []*types.RoleAssignment{selector}, + }}, + }}, + } + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, nil) + s.Require().NoError(err) + }) + + s.Run("required_any rejects when no role resolves", func() { + policy := types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{{ + Signatures: []*types.SignatureRequirement{{ + Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY, + Roles: []*types.RoleAssignment{selector}, + }}, + }}, + } + err := s.app.RegistryKeeper.ValidateRoleChangeAuthorization(s.ctx, policy, entry, nil, nil) + s.Require().Error(err) + s.Require().Contains(err.Error(), "no required signature found in any role") + }) +} diff --git a/x/registry/keeper/query_server_test.go b/x/registry/keeper/query_server_test.go index ccaf30b696..11ae11e788 100644 --- a/x/registry/keeper/query_server_test.go +++ b/x/registry/keeper/query_server_test.go @@ -371,3 +371,174 @@ func (s *KeeperTestSuite) TestQueryHasRole() { }) } } + +// createQueryTestClass stores a minimal, valid registry class for query-server tests and returns it. +func (s *KeeperTestSuite) createQueryTestClass(classID string) types.RegistryClass { + class := types.RegistryClass{ + RegistryClassId: classID, + AssetClassId: s.validNFTClass.Id, + Maintainer: s.user1, + RoleAuthorizations: []types.RoleAuthorization{ + singleSigPolicy(&types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }), + }, + } + s.Require().NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + return class +} + +func (s *KeeperTestSuite) TestQueryRegistryClass() { + stored := s.createQueryTestClass("query-class-1") + queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) + + tests := []struct { + name string + req *types.QueryRegistryClassRequest + expErr string + }{ + { + name: "nil request", + req: nil, + expErr: "empty request", + }, + { + name: "empty registry_class_id", + req: &types.QueryRegistryClassRequest{RegistryClassId: ""}, + expErr: "registry_class_id cannot be empty", + }, + { + name: "class does not exist", + req: &types.QueryRegistryClassRequest{RegistryClassId: "nonexistent"}, + expErr: "registry class", + }, + { + name: "class exists", + req: &types.QueryRegistryClassRequest{RegistryClassId: stored.RegistryClassId}, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + resp, err := queryServer.RegistryClass(s.ctx, tc.req) + if tc.expErr != "" { + assertions.RequireErrorContents(s.T(), err, []string{tc.expErr}) + s.Require().Nil(resp) + } else { + assertions.RequireErrorContents(s.T(), err, nil) + s.Require().NotNil(resp) + s.Require().Equal(stored.RegistryClassId, resp.RegistryClass.RegistryClassId) + s.Require().Equal(stored.Maintainer, resp.RegistryClass.Maintainer) + s.Require().Len(resp.RegistryClass.RoleAuthorizations, 1) + } + }) + } +} + +func (s *KeeperTestSuite) TestQueryRegistryClasses() { + s.createQueryTestClass("query-classes-a") + s.createQueryTestClass("query-classes-b") + s.createQueryTestClass("query-classes-c") + queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) + + tests := []struct { + name string + req *types.QueryRegistryClassesRequest + expErr string + expCount int + }{ + { + name: "nil request", + req: nil, + expErr: "empty request", + }, + { + name: "all classes", + req: &types.QueryRegistryClassesRequest{}, + expCount: 3, + }, + { + name: "pagination limit", + req: &types.QueryRegistryClassesRequest{ + Pagination: &query.PageRequest{Limit: 2, CountTotal: true}, + }, + expCount: 2, + }, + { + name: "pagination offset", + req: &types.QueryRegistryClassesRequest{ + Pagination: &query.PageRequest{Offset: 2, Limit: 2}, + }, + expCount: 1, + }, + } + + for _, tc := range tests { + s.Run(tc.name, func() { + resp, err := queryServer.RegistryClasses(s.ctx, tc.req) + if tc.expErr != "" { + assertions.RequireErrorContents(s.T(), err, []string{tc.expErr}) + s.Require().Nil(resp) + } else { + assertions.RequireErrorContents(s.T(), err, nil) + s.Require().NotNil(resp) + s.Require().Len(resp.RegistryClasses, tc.expCount) + if tc.req.Pagination != nil && tc.req.Pagination.CountTotal { + s.Require().NotNil(resp.Pagination) + } + } + }) + } +} + +func (s *KeeperTestSuite) TestQueryParams() { + queryServer := keeper.NewQueryServer(s.app.RegistryKeeper) + + s.Run("nil request", func() { + resp, err := queryServer.Params(s.ctx, nil) + assertions.RequireErrorContents(s.T(), err, []string{"empty request"}) + s.Require().Nil(resp) + }) + + s.Run("default params", func() { + resp, err := queryServer.Params(s.ctx, &types.QueryParamsRequest{}) + assertions.RequireErrorContents(s.T(), err, nil) + s.Require().NotNil(resp) + s.Require().Empty(resp.Params.RoleAuthorizations) + }) + + s.Run("reflects set params", func() { + s.Require().NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) + resp, err := queryServer.Params(s.ctx, &types.QueryParamsRequest{}) + assertions.RequireErrorContents(s.T(), err, nil) + s.Require().NotNil(resp) + s.Require().Len(resp.Params.RoleAuthorizations, 1) + s.Require().Equal(types.RegistryRole_REGISTRY_ROLE_CONTROLLER, resp.Params.RoleAuthorizations[0].Role) + }) +} + +// TestKeeperRegistryClassLifecycle exercises the keeper-level class store accessors directly: +// HasRegistryClass before/after creation and GetRegistryClasses listing. +func (s *KeeperTestSuite) TestKeeperRegistryClassLifecycle() { + has, err := s.app.RegistryKeeper.HasRegistryClass(s.ctx, "lifecycle-class") + s.Require().NoError(err) + s.Require().False(has) + + got, err := s.app.RegistryKeeper.GetRegistryClass(s.ctx, "lifecycle-class") + s.Require().NoError(err) + s.Require().Nil(got) + + s.createQueryTestClass("lifecycle-class") + + has, err = s.app.RegistryKeeper.HasRegistryClass(s.ctx, "lifecycle-class") + s.Require().NoError(err) + s.Require().True(has) + + classes, _, err := s.app.RegistryKeeper.GetRegistryClasses(s.ctx, nil) + s.Require().NoError(err) + s.Require().Len(classes, 1) + s.Require().Equal("lifecycle-class", classes[0].RegistryClassId) +} diff --git a/x/registry/keeper/role_event_query_acceptance_test.go b/x/registry/keeper/role_event_query_acceptance_test.go index 356a2bc0f5..baaf322e9d 100644 --- a/x/registry/keeper/role_event_query_acceptance_test.go +++ b/x/registry/keeper/role_event_query_acceptance_test.go @@ -180,6 +180,37 @@ func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_PolicyAccumu s.Require().Equal([]string{s.newController}, got["REGISTRY_ROLE_CONTROLLER|ASSIGNMENT_NEW"]) } +// TestEventRoleUpdated_Revoke: a revoke (removing an address from a role) emits EventRoleUpdated +// with the resulting (empty) address set and the prior addresses, authorized via the NFT-owner +// fallback for a role with no policy. +func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_Revoke() { + key := s.mintNFT("evt-revoke") + servicer := genAddr() + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{servicer}}, + }, "")) + + s.resetEvents() + // SERVICER has no policy, so the NFT owner authorizes the revoke. + _, err := s.msgServer.RevokeRole(s.ctx, &types.MsgRevokeRole{ + Signer: s.nftOwner, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{servicer}, + }) + s.Require().NoError(err) + + evts := s.roleUpdatedEvents() + s.Require().Len(evts, 1) + ev := evts[0] + s.Require().Equal("SERVICER", ev.Role) + s.Require().Empty(ev.Addresses) + s.Require().Equal([]string{servicer}, ev.PreviousAddresses) + s.Require().Len(ev.Signers, 1) + s.Require().Equal("NFT_ROLE_NFT_OWNER", ev.Signers[0].Role) + s.Require().Equal([]string{s.nftOwner}, ev.Signers[0].Addresses) +} + // TestEventRoleUpdated_RegistryClassId: an entry registered under a class carries that class id on // the event. func (s *RoleEventAndQueryAcceptanceTestSuite) TestEventRoleUpdated_RegistryClassId() { diff --git a/x/registry/types/authorization_test.go b/x/registry/types/authorization_test.go new file mode 100644 index 0000000000..1d1e0f345b --- /dev/null +++ b/x/registry/types/authorization_test.go @@ -0,0 +1,151 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/provenance-io/provenance/x/registry/types" +) + +func TestSignatureType_Validate(t *testing.T) { + tests := []struct { + name string + typ types.SignatureType + expErr string + }{ + {name: "required_all", typ: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL}, + {name: "required_any", typ: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ANY}, + {name: "unspecified", typ: types.SignatureType_SIGNATURE_TYPE_UNSPECIFIED, expErr: "cannot be unspecified"}, + {name: "undefined value", typ: types.SignatureType(9999), expErr: "unknown"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.typ.Validate() + if tc.expErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErr) + } + }) + } +} + +func TestNftRole_Validate(t *testing.T) { + tests := []struct { + name string + role types.NftRole + expErr string + }{ + {name: "nft_owner", role: types.NftRole_NFT_ROLE_NFT_OWNER}, + {name: "unspecified", role: types.NftRole_NFT_ROLE_UNSPECIFIED, expErr: "cannot be unspecified"}, + {name: "undefined value", role: types.NftRole(9999), expErr: "unknown"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.role.Validate() + if tc.expErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErr) + } + }) + } +} + +func TestAssignment_Validate(t *testing.T) { + tests := []struct { + name string + assignment types.Assignment + expErr string + }{ + {name: "current", assignment: types.Assignment_ASSIGNMENT_CURRENT}, + {name: "new", assignment: types.Assignment_ASSIGNMENT_NEW}, + {name: "unspecified", assignment: types.Assignment_ASSIGNMENT_UNSPECIFIED, expErr: "cannot be unspecified"}, + {name: "undefined value", assignment: types.Assignment(9999), expErr: "unknown"}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.assignment.Validate() + if tc.expErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErr) + } + }) + } +} + +func TestAssignment_IsCurrent(t *testing.T) { + currentVariants := []types.Assignment{ + types.Assignment_ASSIGNMENT_CURRENT, + types.Assignment_ASSIGNMENT_CURRENT_ALL, + types.Assignment_ASSIGNMENT_CURRENT_ANY, + } + for _, a := range currentVariants { + require.True(t, a.IsCurrent(), "%s should be current", a) + } + + nonCurrentVariants := []types.Assignment{ + types.Assignment_ASSIGNMENT_UNSPECIFIED, + types.Assignment_ASSIGNMENT_NEW, + types.Assignment_ASSIGNMENT_NEW_ALL, + types.Assignment_ASSIGNMENT_NEW_ANY, + } + for _, a := range nonCurrentVariants { + require.False(t, a.IsCurrent(), "%s should not be current", a) + } +} + +// TestControllerRoleAuthorizations confirms the shipped example CONTROLLER policy is well-formed: +// it is the fixture governance can install via params or a maintainer can adopt for a class, so it +// must pass the same validation any on-chain class policy must pass. +func TestControllerRoleAuthorizations(t *testing.T) { + auths := types.ControllerRoleAuthorizations() + require.Len(t, auths, 1) + require.Equal(t, types.RegistryRole_REGISTRY_ROLE_CONTROLLER, auths[0].Role) + + // Validate it via the same path a registry class uses. + errs := validateAuthsViaClass(t, auths) + require.NoError(t, errs) +} + +// validateAuthsViaClass runs the given role authorizations through RegistryClass.Validate so the +// example policy is exercised by the production validation code. +func validateAuthsViaClass(t *testing.T, auths []types.RoleAuthorization) error { + t.Helper() + c := validRegistryClass() + c.RoleAuthorizations = auths + return c.Validate() +} + +func TestRoleAuthorizationMapFrom(t *testing.T) { + t.Run("empty", func(t *testing.T) { + m := types.RoleAuthorizationMapFrom(nil) + require.Empty(t, m) + }) + + t.Run("indexed by role", func(t *testing.T) { + auths := []types.RoleAuthorization{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER}, + {Role: types.RegistryRole_REGISTRY_ROLE_SERVICER}, + } + m := types.RoleAuthorizationMapFrom(auths) + require.Len(t, m, 2) + require.Contains(t, m, types.RegistryRole_REGISTRY_ROLE_CONTROLLER) + require.Contains(t, m, types.RegistryRole_REGISTRY_ROLE_SERVICER) + }) + + t.Run("last entry wins on duplicate role", func(t *testing.T) { + auths := []types.RoleAuthorization{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Authorizations: []*types.Authorization{{Description: "first"}}}, + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Authorizations: []*types.Authorization{{Description: "second"}}}, + } + m := types.RoleAuthorizationMapFrom(auths) + require.Len(t, m, 1) + require.Equal(t, "second", m[types.RegistryRole_REGISTRY_ROLE_CONTROLLER].Authorizations[0].Description) + }) +} diff --git a/x/registry/types/class_test.go b/x/registry/types/class_test.go new file mode 100644 index 0000000000..f3938fbc2c --- /dev/null +++ b/x/registry/types/class_test.go @@ -0,0 +1,326 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/provenance-io/provenance/x/registry/types" +) + +// validRoleAuthorization builds a minimal, well-formed SERVICER policy: a single authorization path +// requiring the current controller to sign. It is used as the valid baseline that the negative +// cases mutate. +func validRoleAuthorization() types.RoleAuthorization { + return types.RoleAuthorization{ + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Authorizations: []*types.Authorization{ + { + Description: "controller may manage servicers", + Signatures: []*types.SignatureRequirement{ + { + Type: types.SignatureType_SIGNATURE_TYPE_REQUIRED_ALL, + Roles: []*types.RoleAssignment{ + { + RoleSelector: &types.RoleAssignment_RegistryRole{ + RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, + }, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + }, + }, + }, + }, + }, + }, + } +} + +// validRegistryClass returns a well-formed RegistryClass that each negative case mutates. +func validRegistryClass() *types.RegistryClass { + return &types.RegistryClass{ + RegistryClassId: "dart-loan-v1", + AssetClassId: "asset-class-1", + Maintainer: sdk.AccAddress("class_maintainer_addr_").String(), + RoleAuthorizations: []types.RoleAuthorization{validRoleAuthorization()}, + } +} + +func TestRegistryClass_Validate(t *testing.T) { + tests := []struct { + name string + mutate func(c *types.RegistryClass) + expErr string + }{ + { + name: "valid", + mutate: func(_ *types.RegistryClass) {}, + }, + { + name: "empty registry_class_id", + mutate: func(c *types.RegistryClass) { c.RegistryClassId = "" }, + expErr: "registry_class_id", + }, + { + name: "registry_class_id with illegal character", + mutate: func(c *types.RegistryClass) { c.RegistryClassId = "bad id!" }, + expErr: "alphanumeric", + }, + { + name: "empty asset_class_id", + mutate: func(c *types.RegistryClass) { c.AssetClassId = "" }, + expErr: "asset_class_id", + }, + { + name: "invalid maintainer", + mutate: func(c *types.RegistryClass) { c.Maintainer = "not-bech32" }, + expErr: "maintainer", + }, + { + name: "duplicate role", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations = []types.RoleAuthorization{validRoleAuthorization(), validRoleAuthorization()} + }, + expErr: "duplicate role", + }, + { + name: "unspecified role", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Role = types.RegistryRole_REGISTRY_ROLE_UNSPECIFIED + }, + expErr: "role", + }, + { + name: "no authorization paths", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations = nil + }, + expErr: "at least one authorization path is required", + }, + { + name: "nil authorization path", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations = []*types.Authorization{nil} + }, + expErr: "authorization 0 is nil", + }, + { + name: "no signature requirements", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures = nil + }, + expErr: "at least one signature requirement is required", + }, + { + name: "nil signature requirement", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures = []*types.SignatureRequirement{nil} + }, + expErr: "signature 0 is nil", + }, + { + name: "unspecified signature type", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Type = types.SignatureType_SIGNATURE_TYPE_UNSPECIFIED + }, + expErr: "type", + }, + { + name: "no role selectors", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles = nil + }, + expErr: "at least one role selector is required", + }, + { + name: "nil role selector", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles = []*types.RoleAssignment{nil} + }, + expErr: "role selector is nil", + }, + { + name: "unspecified assignment", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0].Assignment = types.Assignment_ASSIGNMENT_UNSPECIFIED + }, + expErr: "assignment", + }, + { + name: "unspecified registry_role selector", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0].RoleSelector = + &types.RoleAssignment_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_UNSPECIFIED} + }, + expErr: "registry_role", + }, + { + name: "no role selector kind set", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0].RoleSelector = nil + }, + expErr: "a role selector (registry_role, nft_role, or role_priority) must be set", + }, + { + name: "valid nft_role with current assignment", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_NftRole{NftRole: types.NftRole_NFT_ROLE_NFT_OWNER}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + }, + { + name: "nft_role with non-current assignment", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_NftRole{NftRole: types.NftRole_NFT_ROLE_NFT_OWNER}, + Assignment: types.Assignment_ASSIGNMENT_NEW, + } + }, + expErr: "nft_role may only be used with a CURRENT* assignment", + }, + { + name: "unspecified nft_role", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_NftRole{NftRole: types.NftRole_NFT_ROLE_UNSPECIFIED}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + expErr: "nft_role", + }, + { + name: "valid role_priority", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: &types.RolePriority{ + Entries: []*types.RolePriorityEntry{ + {Role: &types.RolePriorityEntry_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_CONTROLLER}}, + {Role: &types.RolePriorityEntry_NftRole{NftRole: types.NftRole_NFT_ROLE_NFT_OWNER}}, + }, + }}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + }, + { + name: "empty role_priority", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: &types.RolePriority{}}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + expErr: "role_priority must contain at least one entry", + }, + { + name: "nil role_priority", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: nil}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + expErr: "role_priority must contain at least one entry", + }, + { + name: "role_priority entry with unspecified registry_role", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: &types.RolePriority{ + Entries: []*types.RolePriorityEntry{ + {Role: &types.RolePriorityEntry_RegistryRole{RegistryRole: types.RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}, + }, + }}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + expErr: "role_priority entry 0: registry_role", + }, + { + name: "role_priority entry nft_role with non-current assignment", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: &types.RolePriority{ + Entries: []*types.RolePriorityEntry{ + {Role: &types.RolePriorityEntry_NftRole{NftRole: types.NftRole_NFT_ROLE_NFT_OWNER}}, + }, + }}, + Assignment: types.Assignment_ASSIGNMENT_NEW, + } + }, + expErr: "role_priority entry 0: nft_role may only be used with a CURRENT* assignment", + }, + { + name: "role_priority entry with no role set", + mutate: func(c *types.RegistryClass) { + c.RoleAuthorizations[0].Authorizations[0].Signatures[0].Roles[0] = &types.RoleAssignment{ + RoleSelector: &types.RoleAssignment_RolePriority{RolePriority: &types.RolePriority{ + Entries: []*types.RolePriorityEntry{{}}, + }}, + Assignment: types.Assignment_ASSIGNMENT_CURRENT, + } + }, + expErr: "role_priority entry 0: a role (registry_role or nft_role) must be set", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + c := validRegistryClass() + tc.mutate(c) + err := c.Validate() + if tc.expErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErr) + } + }) + } +} + +func TestRegistryClass_Validate_Nil(t *testing.T) { + var c *types.RegistryClass + err := c.Validate() + require.Error(t, err) + require.Contains(t, err.Error(), "registry class cannot be nil") +} + +func TestValidateRegistryClassID(t *testing.T) { + tests := []struct { + name string + classID string + expErr string + }{ + {name: "valid alphanumeric", classID: "dart-loan-v1"}, + {name: "valid with dots", classID: "a.b.c"}, + {name: "empty", classID: "", expErr: "must be between"}, + {name: "illegal character", classID: "has space", expErr: "alphanumeric"}, + {name: "underscore not allowed", classID: "a_b", expErr: "alphanumeric"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := types.ValidateRegistryClassID(tc.classID) + if tc.expErr == "" { + require.NoError(t, err) + } else { + require.Error(t, err) + require.Contains(t, err.Error(), tc.expErr) + } + }) + } +} + +func TestValidateRegistryClassID_TooLong(t *testing.T) { + id := make([]byte, types.MaxLenRegistryClassID+1) + for i := range id { + id[i] = 'a' + } + err := types.ValidateRegistryClassID(string(id)) + require.Error(t, err) + require.Contains(t, err.Error(), "must be between") +} From edefe5dea47ffd139e8de8eb5f575de629b857f1 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 10:54:58 -0600 Subject: [PATCH 21/36] registry: address PR #4 review feedback (ledger callers, proto doc) Update ledger keeper test call sites to the 4-arg CreateRegistry signature (pass "" for no registry class), fixing the test compile break. Clarify the registry_class_id proto comment to document the module-params default tier before the legacy NFT-owner fallback, and regenerate the pb.go. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/registry.proto | 3 ++- x/ledger/keeper/authorization_test.go | 4 ++-- x/ledger/keeper/msg_server_test.go | 2 +- x/registry/types/registry.pb.go | 3 ++- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/proto/provenance/registry/v1/registry.proto b/proto/provenance/registry/v1/registry.proto index 68c7e70c78..0fb5ea36a6 100644 --- a/proto/provenance/registry/v1/registry.proto +++ b/proto/provenance/registry/v1/registry.proto @@ -75,7 +75,8 @@ message RegistryEntry { // registry_class_id is the ID of the registry class governing this entry's authorization rules. // If set, role updates for this entry use the authorization rules defined in the referenced - // registry class. If empty, role updates fall back to legacy NFT owner authorization. + // registry class. If empty, role updates fall back to the module params default policies, and + // then to legacy NFT owner authorization for any role with no policy. string registry_class_id = 3 [(gogoproto.jsontag) = "registryClassId,omitempty"]; } diff --git a/x/ledger/keeper/authorization_test.go b/x/ledger/keeper/authorization_test.go index 7bcab72270..4cdac6a99e 100644 --- a/x/ledger/keeper/authorization_test.go +++ b/x/ledger/keeper/authorization_test.go @@ -23,7 +23,7 @@ func (s *TestSuite) TestRequireAuthorization_NoRegistry_NonOwner() { func (s *TestSuite) TestRequireAuthorization_WithServicer() { rk := ®istrytypes.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} roles := []registrytypes.RolesEntry{{Role: registrytypes.RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{s.addr2.String()}}} - s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, rk, roles)) + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, rk, roles, "")) // Servicer allowed err := s.keeper.RequireAuthorization(s.ctx, s.addr2.String(), rk) @@ -38,7 +38,7 @@ func (s *TestSuite) TestRequireAuthorization_WithServicer() { func (s *TestSuite) TestRequireAuthorization_EmptyServicerAddresses() { rk := ®istrytypes.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} roles := []registrytypes.RolesEntry{{Role: registrytypes.RegistryRole_REGISTRY_ROLE_SERVICER, Addresses: []string{}}} - s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, rk, roles)) + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, rk, roles, "")) // Owner allowed err := s.keeper.RequireAuthorization(s.ctx, s.addr1.String(), rk) diff --git a/x/ledger/keeper/msg_server_test.go b/x/ledger/keeper/msg_server_test.go index 183fb7e1fe..d3efad140d 100644 --- a/x/ledger/keeper/msg_server_test.go +++ b/x/ledger/keeper/msg_server_test.go @@ -490,7 +490,7 @@ func (s *MsgServerTestSuite) TestCreate() { // Create a registry if there are roles to grant if len(tc.registryEntries) > 0 { - err := s.app.RegistryKeeper.CreateRegistry(s.ctx, ®istryKey, tc.registryEntries) + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, ®istryKey, tc.registryEntries, "") s.Require().NoError(err, "CreateRegistry error") } } diff --git a/x/registry/types/registry.pb.go b/x/registry/types/registry.pb.go index 58631bc8cb..e25fcc9e26 100644 --- a/x/registry/types/registry.pb.go +++ b/x/registry/types/registry.pb.go @@ -170,7 +170,8 @@ type RegistryEntry struct { Roles []RolesEntry `protobuf:"bytes,2,rep,name=roles,proto3" json:"roles"` // registry_class_id is the ID of the registry class governing this entry's authorization rules. // If set, role updates for this entry use the authorization rules defined in the referenced - // registry class. If empty, role updates fall back to legacy NFT owner authorization. + // registry class. If empty, role updates fall back to the module params default policies, and + // then to legacy NFT owner authorization for any role with no policy. RegistryClassId string `protobuf:"bytes,3,opt,name=registry_class_id,json=registryClassId,proto3" json:"registryClassId,omitempty"` } From 459c86e011ae7c8b2b8e4d46d7feda59ac704903 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 11:03:34 -0600 Subject: [PATCH 22/36] registry: address PR #4 review feedback (event fail-fast, query id validation) emitRoleUpdated now panics if the post-mutation registry read-back errors, rather than silently emitting an EventRoleUpdated with empty current addresses (a nil entry remains valid for a cleaned-up registry). Validate QueryRegistryClassRequest.registry_class_id against the defined id format so malformed ids are rejected at request validation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/keeper.go | 10 +++++++++- x/registry/keeper/query_server_test.go | 5 +++++ x/registry/types/msgs.go | 3 +++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index b91ef6939d..9fc0114e43 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -292,7 +292,15 @@ func (k Keeper) roleChangeSigners(ctx context.Context, before *types.RegistryEnt func (k Keeper) emitRoleUpdated(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, signers []types.RoleSigner) { previous := before.GetRoleAddrs(role) var current []string - if after, err := k.GetRegistry(ctx, before.Key); err == nil && after != nil { + after, err := k.GetRegistry(ctx, before.Key) + if err != nil { + // The mutation has already been committed; a read-back error means the store is + // inconsistent. Fail fast rather than emit a misleading audit event with empty addresses. + panic(fmt.Errorf("could not read back registry %q for EventRoleUpdated: %w", before.Key.String(), err)) + } + // after may legitimately be nil if the role change removed the entry's last role and the entry + // was cleaned up; in that case current is correctly empty. + if after != nil { current = after.GetRoleAddrs(role) } k.EmitEvent(ctx, types.NewEventRoleUpdated(before.Key, before.RegistryClassId, role, previous, current, signers)) diff --git a/x/registry/keeper/query_server_test.go b/x/registry/keeper/query_server_test.go index 11ae11e788..01b095279a 100644 --- a/x/registry/keeper/query_server_test.go +++ b/x/registry/keeper/query_server_test.go @@ -408,6 +408,11 @@ func (s *KeeperTestSuite) TestQueryRegistryClass() { req: &types.QueryRegistryClassRequest{RegistryClassId: ""}, expErr: "registry_class_id cannot be empty", }, + { + name: "malformed registry_class_id", + req: &types.QueryRegistryClassRequest{RegistryClassId: "bad id!"}, + expErr: "alphanumeric", + }, { name: "class does not exist", req: &types.QueryRegistryClassRequest{RegistryClassId: "nonexistent"}, diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 7f88d85466..7d03bd1ab4 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -342,6 +342,9 @@ func (m QueryRegistryClassRequest) Validate() error { if len(m.RegistryClassId) == 0 { return NewErrCodeInvalidField("registry_class_id", "registry_class_id cannot be empty") } + if err := ValidateRegistryClassID(m.RegistryClassId); err != nil { + return NewErrCodeInvalidField("registry_class_id", "%v", err) + } return nil } From 892b4ae0f3e728b5a80c4816c1c7cd1f14ff0b1d Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 11:23:26 -0600 Subject: [PATCH 23/36] registry: remove product-specific naming from examples and tests Replace the company-specific "DART" product name with generic language throughout the registry module. Rename the acceptance test suite and example fixture, genericize identifiers (loan-registry-v1 class id, participant role policy builders), the spec README, and the proto/pb.go doc comments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../registry/v1/authorization.proto | 2 +- x/registry/client/cli/tx.go | 2 +- ...ticipant_role_policies_acceptance_test.go} | 80 +++++++++---------- x/registry/spec/examples/README.md | 16 ++-- .../spec/examples/dart_registry_class.json | 1 - .../spec/examples/example_registry_class.json | 1 + x/registry/types/authorization.pb.go | 2 +- x/registry/types/class_test.go | 4 +- 8 files changed, 54 insertions(+), 54 deletions(-) rename x/registry/keeper/{dart_role_policies_acceptance_test.go => participant_role_policies_acceptance_test.go} (87%) delete mode 100644 x/registry/spec/examples/dart_registry_class.json create mode 100644 x/registry/spec/examples/example_registry_class.json diff --git a/proto/provenance/registry/v1/authorization.proto b/proto/provenance/registry/v1/authorization.proto index 7d87184a5f..338c8cf8f5 100644 --- a/proto/provenance/registry/v1/authorization.proto +++ b/proto/provenance/registry/v1/authorization.proto @@ -13,7 +13,7 @@ import "provenance/registry/v1/registry.proto"; // It defines authorization rules for an entire NFT collection (asset class), allowing the // maintainer to establish custom role update policies without requiring governance proposals. message RegistryClass { - // registry_class_id is the unique identifier for this registry class (e.g. "dart-loan-v1"). + // registry_class_id is the unique identifier for this registry class (e.g. "loan-registry-v1"). // It is required since NFT classes do not have an inherent owner, and must be unique across // all registry classes. string registry_class_id = 1 [(gogoproto.jsontag) = "registryClassId,omitempty"]; diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index edf87d9f86..e0b7a7ee67 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -307,7 +307,7 @@ func CmdCreateRegistryClass() *cobra.Command { The file is a proto-JSON RegistryClass, e.g.: { - "registryClassId": "dart-loan-v1", + "registryClassId": "loan-registry-v1", "asset_class_id": "loan.asset", "maintainer": "pb1...", "role_authorizations": [ { "role": "REGISTRY_ROLE_CONTROLLER", "authorizations": [ ... ] } ] diff --git a/x/registry/keeper/dart_role_policies_acceptance_test.go b/x/registry/keeper/participant_role_policies_acceptance_test.go similarity index 87% rename from x/registry/keeper/dart_role_policies_acceptance_test.go rename to x/registry/keeper/participant_role_policies_acceptance_test.go index c706aa96fa..fedf7d2cea 100644 --- a/x/registry/keeper/dart_role_policies_acceptance_test.go +++ b/x/registry/keeper/participant_role_policies_acceptance_test.go @@ -20,12 +20,12 @@ import ( "github.com/provenance-io/provenance/x/registry/types" ) -// DartRolePoliciesAcceptanceTestSuite proves that the DART participant role policies described in -// the ticket (sc-512248, requirements.md §"DART Participant Roles") are expressible as ordinary +// ParticipantRolePoliciesAcceptanceTestSuite proves that the participant role policies described in +// the ticket (sc-512248, requirements.md §"Participant Roles") are expressible as ordinary // RegistryClass.role_authorizations data and are correctly evaluated by the policy engine — without // any hard-coded, per-role chain logic. The same policies double as the example fixture shipped in -// x/registry/spec/examples/dart_registry_class.json. -type DartRolePoliciesAcceptanceTestSuite struct { +// x/registry/spec/examples/example_registry_class.json. +type ParticipantRolePoliciesAcceptanceTestSuite struct { suite.Suite app *app.App @@ -41,13 +41,13 @@ type DartRolePoliciesAcceptanceTestSuite struct { nftOwner string } -func TestDartRolePoliciesAcceptanceTestSuite(t *testing.T) { - suite.Run(t, new(DartRolePoliciesAcceptanceTestSuite)) +func TestParticipantRolePoliciesAcceptanceTestSuite(t *testing.T) { + suite.Run(t, new(ParticipantRolePoliciesAcceptanceTestSuite)) } -const dartClassID = "dart-loan-v1" +const exampleClassID = "loan-registry-v1" -func (s *DartRolePoliciesAcceptanceTestSuite) SetupTest() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) SetupTest() { s.app = app.Setup(s.T()) s.ctx = s.app.BaseApp.NewContextLegacy(false, cmtproto.Header{Time: time.Now()}).WithBlockTime(time.Now()) @@ -58,35 +58,35 @@ func (s *DartRolePoliciesAcceptanceTestSuite) SetupTest() { s.maintainer = genAddr() s.nftOwner = genAddr() - s.nftClass = nft.Class{Id: "dart-roles-test-nft-class"} + s.nftClass = nft.Class{Id: "participant-roles-test-nft-class"} s.nftKeeper.SaveClass(s.ctx, s.nftClass) - // Install the full DART registry class so every classed entry is governed by these policies. + // Install the full participant registry class so every classed entry is governed by these policies. _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ Signer: s.maintainer, - RegistryClassId: dartClassID, + RegistryClassId: exampleClassID, AssetClassId: s.nftClass.Id, Maintainer: s.maintainer, - RoleAuthorizations: dartRoleAuthorizations(), + RoleAuthorizations: participantRoleAuthorizations(), }) s.Require().NoError(err) } -// registerWithRoles mints an NFT owned by s.nftOwner, registers it under the DART class, and seeds +// registerWithRoles mints an NFT owned by s.nftOwner, registers it under the participant class, and seeds // the given initial roles. It returns the registry key. -func (s *DartRolePoliciesAcceptanceTestSuite) registerWithRoles(id string, roles []types.RolesEntry) *types.RegistryKey { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) registerWithRoles(id string, roles []types.RolesEntry) *types.RegistryKey { n := nft.NFT{ClassId: s.nftClass.Id, Id: id} ownerAddr, err := sdk.AccAddressFromBech32(s.nftOwner) s.Require().NoError(err) s.Require().NoError(s.nftKeeper.Mint(s.ctx, n, ownerAddr)) key := &types.RegistryKey{AssetClassId: s.nftClass.Id, NftId: id} - s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles, dartClassID)) + s.Require().NoError(s.registryKeeper.CreateRegistry(s.ctx, key, roles, exampleClassID)) return key } // propose opens a pending role change and returns the change id and whether it applied immediately. -func (s *DartRolePoliciesAcceptanceTestSuite) propose(key *types.RegistryKey, signer string, role types.RegistryRole, addrs []string) (string, bool) { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) propose(key *types.RegistryKey, signer string, role types.RegistryRole, addrs []string) (string, bool) { resp, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ Signer: signer, Key: key, @@ -98,7 +98,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) propose(key *types.RegistryKey, si // proposeErr attempts a proposal expected to be rejected because the proposer is not an eligible // approver of the change, and returns the error. -func (s *DartRolePoliciesAcceptanceTestSuite) proposeErr(key *types.RegistryKey, signer string, role types.RegistryRole, addrs []string) error { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) proposeErr(key *types.RegistryKey, signer string, role types.RegistryRole, addrs []string) error { _, err := s.msgServer.ProposeRoleChange(s.ctx, &types.MsgProposeRoleChange{ Signer: signer, Key: key, @@ -108,7 +108,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) proposeErr(key *types.RegistryKey, } // approve records an approval for a pending change and returns whether it applied. -func (s *DartRolePoliciesAcceptanceTestSuite) approve(changeID, signer string) bool { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) approve(changeID, signer string) bool { resp, err := s.msgServer.ApproveRoleChange(s.ctx, &types.MsgApproveRoleChange{ Signer: signer, ChangeId: changeID, @@ -118,7 +118,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) approve(changeID, signer string) b } // roleAddresses returns the addresses currently assigned to role on the entry. -func (s *DartRolePoliciesAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) roleAddresses(key *types.RegistryKey, role types.RegistryRole) []string { entry, err := s.registryKeeper.GetRegistry(s.ctx, key) s.Require().NoError(err) s.Require().NotNil(entry) @@ -134,7 +134,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) roleAddresses(key *types.RegistryK // TestOriginator: an originator update requires the current originator (or NFT owner if unset) plus // the incoming new originator. -func (s *DartRolePoliciesAcceptanceTestSuite) TestOriginator() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestOriginator() { role := types.RegistryRole_REGISTRY_ROLE_ORIGINATOR currentOriginator := genAddr() newOriginator := genAddr() @@ -172,7 +172,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) TestOriginator() { // TestLienOwnerStandard: a standard lien owner transfer requires the current lien owner, the current // Secured Party for Lien (if set), and the new lien owner. -func (s *DartRolePoliciesAcceptanceTestSuite) TestLienOwnerStandard() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestLienOwnerStandard() { lienOwner := types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER securedParty := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN @@ -214,7 +214,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) TestLienOwnerStandard() { // TestLienOwnerForeclosure: the Secured Party for Lien can unilaterally become the Lien Owner. A // single proposal by the secured party (assigning the lien owner role to itself) satisfies the // dedicated foreclosure authorization path. -func (s *DartRolePoliciesAcceptanceTestSuite) TestLienOwnerForeclosure() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestLienOwnerForeclosure() { lienOwner := types.RegistryRole_REGISTRY_ROLE_LIEN_OWNER securedParty := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN @@ -246,7 +246,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) TestLienOwnerForeclosure() { } // TestControllerForeclosure: the Secured Party for eNote can unilaterally become the Controller. -func (s *DartRolePoliciesAcceptanceTestSuite) TestControllerForeclosure() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestControllerForeclosure() { controller := types.RegistryRole_REGISTRY_ROLE_CONTROLLER securedParty := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE @@ -266,7 +266,7 @@ func (s *DartRolePoliciesAcceptanceTestSuite) TestControllerForeclosure() { // TestServicer exercises a policy whose conditional requirement uses a role_priority selector // (Secured Party for eNote, falling back to Pledgee). Granting a servicer requires the current // controller, the conditional approver, and the new servicer. -func (s *DartRolePoliciesAcceptanceTestSuite) TestServicer() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestServicer() { servicer := types.RegistryRole_REGISTRY_ROLE_SERVICER controller := types.RegistryRole_REGISTRY_ROLE_CONTROLLER spEnote := types.RegistryRole_REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE @@ -305,17 +305,17 @@ func (s *DartRolePoliciesAcceptanceTestSuite) TestServicer() { }) } -// TestAllDartPoliciesCoexist confirms the full set validates and persists together. -func (s *DartRolePoliciesAcceptanceTestSuite) TestAllDartPoliciesCoexist() { - got, err := s.registryKeeper.GetRegistryClass(s.ctx, dartClassID) +// TestAllParticipantPoliciesCoexist confirms the full set validates and persists together. +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestAllParticipantPoliciesCoexist() { + got, err := s.registryKeeper.GetRegistryClass(s.ctx, exampleClassID) s.Require().NoError(err) s.Require().NotNil(got) - s.Require().Len(got.RoleAuthorizations, len(dartRoleAuthorizations())) + s.Require().Len(got.RoleAuthorizations, len(participantRoleAuthorizations())) } // TestMalformedPolicyRejected verifies the deepened create-time validation rejects malformed // authorization paths instead of letting them fail closed only at evaluation time. -func (s *DartRolePoliciesAcceptanceTestSuite) TestMalformedPolicyRejected() { +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestMalformedPolicyRejected() { cases := []struct { name string auth types.RoleAuthorization @@ -388,36 +388,36 @@ func (s *DartRolePoliciesAcceptanceTestSuite) TestMalformedPolicyRejected() { } // TestExampleFixtureInSync verifies the committed example fixture proto-JSON decodes to exactly the -// DART policies built in code. Set REGEN_DART_FIXTURE=1 to (re)generate the fixture file. -func (s *DartRolePoliciesAcceptanceTestSuite) TestExampleFixtureInSync() { - path := filepath.Join("..", "spec", "examples", "dart_registry_class.json") +// participant policies built in code. Set REGEN_EXAMPLE_FIXTURE=1 to (re)generate the fixture file. +func (s *ParticipantRolePoliciesAcceptanceTestSuite) TestExampleFixtureInSync() { + path := filepath.Join("..", "spec", "examples", "example_registry_class.json") want := types.RegistryClass{ - RegistryClassId: dartClassID, + RegistryClassId: exampleClassID, AssetClassId: "loan.asset", Maintainer: "pb1maintainerplaceholder0000000000000000000", - RoleAuthorizations: dartRoleAuthorizations(), + RoleAuthorizations: participantRoleAuthorizations(), } - if os.Getenv("REGEN_DART_FIXTURE") == "1" { + if os.Getenv("REGEN_EXAMPLE_FIXTURE") == "1" { bz, err := s.app.AppCodec().MarshalJSON(&want) s.Require().NoError(err) s.Require().NoError(os.WriteFile(path, append(bz, '\n'), 0o644)) } bz, err := os.ReadFile(path) - s.Require().NoError(err, "example fixture missing; run with REGEN_DART_FIXTURE=1 to generate") + s.Require().NoError(err, "example fixture missing; run with REGEN_EXAMPLE_FIXTURE=1 to generate") var got types.RegistryClass s.Require().NoError(s.app.AppCodec().UnmarshalJSON(bz, &got)) s.Require().Equal(want.RoleAuthorizations, got.RoleAuthorizations, - "example fixture is out of sync with code; run with REGEN_DART_FIXTURE=1 to regenerate") + "example fixture is out of sync with code; run with REGEN_EXAMPLE_FIXTURE=1 to regenerate") } -// --- DART policy builders (mirror requirements.md §"DART Participant Roles") --------------------- +// --- Participant policy builders (mirror requirements.md §"Participant Roles") --------------------- -// dartRoleAuthorizations returns the full set of DART participant role policies. -func dartRoleAuthorizations() []types.RoleAuthorization { +// participantRoleAuthorizations returns the full set of participant role policies. +func participantRoleAuthorizations() []types.RoleAuthorization { return []types.RoleAuthorization{ originatorPolicy(), lienOwnerPolicy(), diff --git a/x/registry/spec/examples/README.md b/x/registry/spec/examples/README.md index 49300d66b0..8b3eb4875d 100644 --- a/x/registry/spec/examples/README.md +++ b/x/registry/spec/examples/README.md @@ -2,26 +2,26 @@ This directory holds example registry classes that can be created with the registry CLI. -## `dart_registry_class.json` +## `example_registry_class.json` -A complete DART (Digital Asset Registry Technology) registry class expressing the participant role -policies from the ticket (sc-512248) as ordinary `role_authorizations` data: Originator, Lien Owner -(with foreclosure), Controller (with foreclosure), Secured Party for Lien, Secured Party for eNote, -Pledgee, Servicer, and Sub-servicer. +A complete example registry class expressing the participant role policies from the ticket +(sc-512248) as ordinary `role_authorizations` data: Originator, Lien Owner (with foreclosure), +Controller (with foreclosure), Secured Party for Lien, Secured Party for eNote, Pledgee, Servicer, +and Sub-servicer. These policies are evaluated by the registry's authorization engine; none of them are hard-coded into the chain. A maintainer installs them per asset class: ```bash -provenanced tx registry create-registry-class dart_registry_class.json --from +provenanced tx registry create-registry-class example_registry_class.json --from ``` Before submitting, set `registry_class_id`, `asset_class_id`, and `maintainer` to your values (the `maintainer` must equal the `--from` signer, or it can be omitted to default to that signer). The file is kept in sync with the policy builders in -`x/registry/keeper/dart_role_policies_acceptance_test.go`; regenerate it with: +`x/registry/keeper/participant_role_policies_acceptance_test.go`; regenerate it with: ```bash -REGEN_DART_FIXTURE=1 go test ./x/registry/keeper/ -run TestDartRolePoliciesAcceptanceTestSuite/TestExampleFixtureInSync +REGEN_EXAMPLE_FIXTURE=1 go test ./x/registry/keeper/ -run TestParticipantRolePoliciesAcceptanceTestSuite/TestExampleFixtureInSync ``` diff --git a/x/registry/spec/examples/dart_registry_class.json b/x/registry/spec/examples/dart_registry_class.json deleted file mode 100644 index 3c35111fa7..0000000000 --- a/x/registry/spec/examples/dart_registry_class.json +++ /dev/null @@ -1 +0,0 @@ -{"registry_class_id":"dart-loan-v1","asset_class_id":"loan.asset","maintainer":"pb1maintainerplaceholder0000000000000000000","role_authorizations":[{"role":"REGISTRY_ROLE_ORIGINATOR","authorizations":[{"description":"Assign originator (requires current and new originator approval)","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_ORIGINATOR"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_ORIGINATOR","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_LIEN_OWNER","authorizations":[{"description":"Transfer requiring current lien owner approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_LIEN_OWNER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_LIEN_OWNER","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Foreclosure: Secured Party for Lien can unilaterally become Lien Owner in case of default","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_LIEN_OWNER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_CONTROLLER","authorizations":[{"description":"Transfer requiring current controller approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_CONTROLLER","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Foreclosure: Secured Party for eNote can unilaterally become Controller in case of default","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_CONTROLLER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","authorizations":[{"description":"Update by current lien owner","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_LIEN_OWNER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Update by current secured party for lien","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","authorizations":[{"description":"Update by current controller","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Update by current secured party for eNote","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_PLEDGEE","authorizations":[{"description":"Update pledgee with value owner approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"nft_role":"NFT_ROLE_NFT_OWNER","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_PLEDGEE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_PLEDGEE","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SERVICER","authorizations":[{"description":"Update servicer with owner/controller approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE"},{"registry_role":"REGISTRY_ROLE_PLEDGEE"}]},"assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SUBSERVICER","authorizations":[{"description":"Update sub-servicer with owner/controller and servicer approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE"},{"registry_role":"REGISTRY_ROLE_PLEDGEE"}]},"assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SUBSERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SUBSERVICER","assignment":"ASSIGNMENT_NEW"}]}]}]}]} diff --git a/x/registry/spec/examples/example_registry_class.json b/x/registry/spec/examples/example_registry_class.json new file mode 100644 index 0000000000..a54c9ed1de --- /dev/null +++ b/x/registry/spec/examples/example_registry_class.json @@ -0,0 +1 @@ +{"registry_class_id":"loan-registry-v1","asset_class_id":"loan.asset","maintainer":"pb1maintainerplaceholder0000000000000000000","role_authorizations":[{"role":"REGISTRY_ROLE_ORIGINATOR","authorizations":[{"description":"Assign originator (requires current and new originator approval)","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_ORIGINATOR"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_ORIGINATOR","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_LIEN_OWNER","authorizations":[{"description":"Transfer requiring current lien owner approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_LIEN_OWNER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_LIEN_OWNER","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Foreclosure: Secured Party for Lien can unilaterally become Lien Owner in case of default","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_LIEN_OWNER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_CONTROLLER","authorizations":[{"description":"Transfer requiring current controller approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_CONTROLLER","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Foreclosure: Secured Party for eNote can unilaterally become Controller in case of default","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_CONTROLLER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","authorizations":[{"description":"Update by current lien owner","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_LIEN_OWNER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Update by current secured party for lien","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_LIEN","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","authorizations":[{"description":"Update by current controller","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_NEW"}]}]},{"description":"Update by current secured party for eNote","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_PLEDGEE","authorizations":[{"description":"Update pledgee with value owner approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"nft_role":"NFT_ROLE_NFT_OWNER","assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"registry_role":"REGISTRY_ROLE_PLEDGEE","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_PLEDGEE","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SERVICER","authorizations":[{"description":"Update servicer with owner/controller approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE"},{"registry_role":"REGISTRY_ROLE_PLEDGEE"}]},"assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_NEW"}]}]}]},{"role":"REGISTRY_ROLE_SUBSERVICER","authorizations":[{"description":"Update sub-servicer with owner/controller and servicer approval","signatures":[{"type":"SIGNATURE_TYPE_REQUIRED_ALL","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_CONTROLLER"},{"nft_role":"NFT_ROLE_NFT_OWNER"}]},"assignment":"ASSIGNMENT_CURRENT"}]},{"type":"SIGNATURE_TYPE_REQUIRED_ALL_IF_SET","roles":[{"role_priority":{"entries":[{"registry_role":"REGISTRY_ROLE_SECURED_PARTY_FOR_ENOTE"},{"registry_role":"REGISTRY_ROLE_PLEDGEE"}]},"assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SUBSERVICER","assignment":"ASSIGNMENT_CURRENT"},{"registry_role":"REGISTRY_ROLE_SUBSERVICER","assignment":"ASSIGNMENT_NEW"}]}]}]}]} diff --git a/x/registry/types/authorization.pb.go b/x/registry/types/authorization.pb.go index ce3c8b4218..6e5f7c24fc 100644 --- a/x/registry/types/authorization.pb.go +++ b/x/registry/types/authorization.pb.go @@ -158,7 +158,7 @@ func (Assignment) EnumDescriptor() ([]byte, []int) { // It defines authorization rules for an entire NFT collection (asset class), allowing the // maintainer to establish custom role update policies without requiring governance proposals. type RegistryClass struct { - // registry_class_id is the unique identifier for this registry class (e.g. "dart-loan-v1"). + // registry_class_id is the unique identifier for this registry class (e.g. "loan-registry-v1"). // It is required since NFT classes do not have an inherent owner, and must be unique across // all registry classes. RegistryClassId string `protobuf:"bytes,1,opt,name=registry_class_id,json=registryClassId,proto3" json:"registryClassId,omitempty"` diff --git a/x/registry/types/class_test.go b/x/registry/types/class_test.go index f3938fbc2c..f7783a9fb7 100644 --- a/x/registry/types/class_test.go +++ b/x/registry/types/class_test.go @@ -40,7 +40,7 @@ func validRoleAuthorization() types.RoleAuthorization { // validRegistryClass returns a well-formed RegistryClass that each negative case mutates. func validRegistryClass() *types.RegistryClass { return &types.RegistryClass{ - RegistryClassId: "dart-loan-v1", + RegistryClassId: "loan-registry-v1", AssetClassId: "asset-class-1", Maintainer: sdk.AccAddress("class_maintainer_addr_").String(), RoleAuthorizations: []types.RoleAuthorization{validRoleAuthorization()}, @@ -295,7 +295,7 @@ func TestValidateRegistryClassID(t *testing.T) { classID string expErr string }{ - {name: "valid alphanumeric", classID: "dart-loan-v1"}, + {name: "valid alphanumeric", classID: "loan-registry-v1"}, {name: "valid with dots", classID: "a.b.c"}, {name: "empty", classID: "", expErr: "must be between"}, {name: "illegal character", classID: "has space", expErr: "alphanumeric"}, From cf83336f5fe9acfc7f59d7ae6660fcc5d864fec8 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 11:26:13 -0600 Subject: [PATCH 24/36] registry: parse validate-role-change role_updates via proto-JSON codec The validate-role-change CLI documented string enum values (e.g. "REGISTRY_ROLE_CONTROLLER") but used encoding/json, which only accepts numeric enum values for the int32-backed role field. Decode the role_updates argument through the proto-JSON codec (wrapped in the request message) so the documented string enum names work. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/client/cli/query.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/x/registry/client/cli/query.go b/x/registry/client/cli/query.go index b8c18195e4..4e0f59d7af 100644 --- a/x/registry/client/cli/query.go +++ b/x/registry/client/cli/query.go @@ -2,7 +2,6 @@ package cli import ( "context" - "encoding/json" "fmt" "github.com/spf13/cobra" @@ -333,25 +332,27 @@ Pass one or more --approver flags with the candidate approver addresses.`, return err } - var roleUpdates []types.RoleUpdate - if err := json.Unmarshal([]byte(args[2]), &roleUpdates); err != nil { - return fmt.Errorf("failed to parse role_updates_json: %w", err) - } - approvers, err := cmd.Flags().GetStringArray("approver") if err != nil { return err } + // Parse via the proto-JSON codec so enum fields (e.g. role) accept their string names + // like "REGISTRY_ROLE_CONTROLLER" rather than only numeric values. The role_updates_json + // argument is the array; wrap it in the request message to decode it. + req := &types.QueryValidateRoleChangeRequest{} + wrapped := fmt.Sprintf(`{"role_updates": %s}`, args[2]) + if err := clientCtx.Codec.UnmarshalJSON([]byte(wrapped), req); err != nil { + return fmt.Errorf("failed to parse role_updates_json: %w", err) + } + req.Key = &types.RegistryKey{ + AssetClassId: args[0], + NftId: args[1], + } + req.Approvers = approvers + queryClient := types.NewQueryClient(clientCtx) - res, err := queryClient.ValidateRoleChange(context.Background(), &types.QueryValidateRoleChangeRequest{ - Key: &types.RegistryKey{ - AssetClassId: args[0], - NftId: args[1], - }, - RoleUpdates: roleUpdates, - Approvers: approvers, - }) + res, err := queryClient.ValidateRoleChange(context.Background(), req) if err != nil { return err } From 9611340ca632755a730da6123dcb6f4b0a2df4cb Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 11:29:41 -0600 Subject: [PATCH 25/36] registry: remove internal ticket references from comments and docs Drop the company-internal ticket identifier (sc-512248) and generic "ticket" mentions from registry test suite comments, the example spec README, and the ControllerRoleAuthorizations doc comment, replacing them with generic language. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../keeper/participant_role_policies_acceptance_test.go | 2 +- .../keeper/role_change_accumulation_acceptance_test.go | 8 ++++---- x/registry/spec/examples/README.md | 4 ++-- x/registry/types/authorization.go | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/x/registry/keeper/participant_role_policies_acceptance_test.go b/x/registry/keeper/participant_role_policies_acceptance_test.go index fedf7d2cea..d8606c0f35 100644 --- a/x/registry/keeper/participant_role_policies_acceptance_test.go +++ b/x/registry/keeper/participant_role_policies_acceptance_test.go @@ -21,7 +21,7 @@ import ( ) // ParticipantRolePoliciesAcceptanceTestSuite proves that the participant role policies described in -// the ticket (sc-512248, requirements.md §"Participant Roles") are expressible as ordinary +// the requirements (§"Participant Roles") are expressible as ordinary // RegistryClass.role_authorizations data and are correctly evaluated by the policy engine — without // any hard-coded, per-role chain logic. The same policies double as the example fixture shipped in // x/registry/spec/examples/example_registry_class.json. diff --git a/x/registry/keeper/role_change_accumulation_acceptance_test.go b/x/registry/keeper/role_change_accumulation_acceptance_test.go index b96d836183..39106264df 100644 --- a/x/registry/keeper/role_change_accumulation_acceptance_test.go +++ b/x/registry/keeper/role_change_accumulation_acceptance_test.go @@ -21,7 +21,7 @@ import ( "github.com/provenance-io/provenance/x/registry/types" ) -// RoleChangeAccumulationAcceptanceTestSuite implements the ticket (sc-512248) Controller +// RoleChangeAccumulationAcceptanceTestSuite implements the Controller // authorization matrix using Option B: single-signer messages whose approvals accumulate in // registry state until the role's policy is satisfied, at which point the change auto-applies. // @@ -309,7 +309,7 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerSet_WithSecure // --- AuthZ delegation ----------------------------------------------------------------- // -// The single-signer accumulation model is the whole reason Option B satisfies the ticket's authz +// The single-signer accumulation model is the whole reason Option B satisfies the authz // requirement: because each MsgApproveRoleChange has exactly one signer, a party can grant another // account authority to submit its approval via authz MsgExec. This is impossible with the native // multi-signer MsgGrantRole (see authorization_acceptance_test.go, @@ -420,7 +420,7 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_NoSecur s.Require().Nil(pending, "pending change should be removed after it applies") } -// TestControllerUpdate_RejectMatrix_ViaAuthz re-runs the ticket's reject scenarios with every +// TestControllerUpdate_RejectMatrix_ViaAuthz re-runs the reject scenarios with every // approval (and the proposal) delegated through authz MsgExec, proving that an incomplete set of // authz-delegated approvals never satisfies the policy. A neutral executor carries each party's // delegated authority, so no required party ever signs a transaction directly. @@ -512,7 +512,7 @@ func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerRevoke_Accumul // // The accumulation engine re-resolves roles against live registry state on every approval, so an // approval recorded by a party that is no longer the current role holder cannot satisfy the policy. -// This is the ticket's invalidation guarantee without a separate expiry mechanism. +// This is the invalidation guarantee without a separate expiry mechanism. func (s *RoleChangeAccumulationAcceptanceTestSuite) TestControllerUpdate_InvalidationOnRoleChange() { controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER diff --git a/x/registry/spec/examples/README.md b/x/registry/spec/examples/README.md index 8b3eb4875d..580fece7a8 100644 --- a/x/registry/spec/examples/README.md +++ b/x/registry/spec/examples/README.md @@ -4,8 +4,8 @@ This directory holds example registry classes that can be created with the regis ## `example_registry_class.json` -A complete example registry class expressing the participant role policies from the ticket -(sc-512248) as ordinary `role_authorizations` data: Originator, Lien Owner (with foreclosure), +A complete example registry class expressing the participant role policies as ordinary +`role_authorizations` data: Originator, Lien Owner (with foreclosure), Controller (with foreclosure), Secured Party for Lien, Secured Party for eNote, Pledgee, Servicer, and Sub-servicer. diff --git a/x/registry/types/authorization.go b/x/registry/types/authorization.go index eb26966d8a..34fd606f41 100644 --- a/x/registry/types/authorization.go +++ b/x/registry/types/authorization.go @@ -53,7 +53,7 @@ func (a Assignment) IsCurrent() bool { } } -// ControllerRoleAuthorizations returns the example CONTROLLER authorization policy from the ticket. +// ControllerRoleAuthorizations returns an example CONTROLLER authorization policy. // This is NOT the default chain behavior: by default the registry module has no role policies and // every role change falls back to legacy NFT-owner authorization. This policy set is provided as a // reusable example that governance can install via MsgUpdateParams, or that a registry-class From 1a533bb249e7a35f6bb1bc1a6aaab66e05a72d13 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 13:33:49 -0600 Subject: [PATCH 26/36] fix(registry): enforce registry class asset-class match for entries A registry entry referencing a registry_class_id only verified the class existed, not that the class's asset_class_id matched the entry's. A class is scoped to a single asset class, so a mismatch would resolve role authorization against the wrong policy tier across unrelated collections. Enforce the match at every write/read site: RegisterNFT, RegistryBulkUpdate, GenesisState.Validate, and (defense-in-depth, fail-closed) the runtime roleAuthorizationsForEntry resolver. Add ErrRegistryClassAssetMismatch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/class.go | 27 +++++++++ x/registry/keeper/msg_server.go | 24 ++------ .../keeper/registry_class_acceptance_test.go | 20 +++++++ x/registry/types/errors.go | 60 +++++++++++-------- x/registry/types/genesis.go | 19 ++++-- x/registry/types/genesis_test.go | 28 +++++++++ 6 files changed, 131 insertions(+), 47 deletions(-) diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go index e7fbe2842c..cbaaacc835 100644 --- a/x/registry/keeper/class.go +++ b/x/registry/keeper/class.go @@ -29,6 +29,27 @@ func (k Keeper) HasRegistryClass(ctx context.Context, registryClassID string) (b return k.RegistryClasses.Has(ctx, registryClassID) } +// validateRegistryClassForEntry verifies that an entry may reference the given registry class id. +// When the id is empty there is nothing to check. Otherwise the class must exist and its asset +// class id must match the entry's asset class id; a registry class only governs entries within its +// own asset class, so a mismatch would resolve authorization against the wrong policy tier. +func (k Keeper) validateRegistryClassForEntry(ctx context.Context, registryClassID, assetClassID string) error { + if registryClassID == "" { + return nil + } + class, err := k.GetRegistryClass(ctx, registryClassID) + if err != nil { + return err + } + if class == nil { + return types.NewErrCodeRegistryClassNotFound(registryClassID) + } + if class.AssetClassId != assetClassID { + return types.NewErrCodeRegistryClassAssetMismatch(registryClassID, class.AssetClassId, assetClassID) + } + return nil +} + // SetRegistryClass stores a registry class in state. func (k Keeper) SetRegistryClass(ctx context.Context, class types.RegistryClass) error { return k.RegistryClasses.Set(ctx, class.RegistryClassId, class) @@ -120,6 +141,12 @@ func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.Reg panic(fmt.Errorf("could not resolve registry class %q for authorization: %w", entry.RegistryClassId, err)) } if class != nil { + // Defense in depth: enforcement at write time (register, bulk update, genesis) keeps the + // referenced class scoped to the entry's asset class. If a mismatched class is ever found + // in state, fail fast rather than resolve authorization against the wrong policy tier. + if entry.Key != nil && class.AssetClassId != entry.Key.AssetClassId { + panic(types.NewErrCodeRegistryClassAssetMismatch(entry.RegistryClassId, class.AssetClassId, entry.Key.AssetClassId)) + } return types.RoleAuthorizationMapFrom(class.RoleAuthorizations) } } diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 9174afc6a8..6293e49d07 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -34,15 +34,9 @@ func (k msgServer) RegisterNFT(ctx context.Context, msg *types.MsgRegisterNFT) ( return nil, err } - // If a registry class is referenced, it must exist. - if msg.RegistryClassId != "" { - has, err := k.HasRegistryClass(ctx, msg.RegistryClassId) - if err != nil { - return nil, err - } - if !has { - return nil, types.NewErrCodeRegistryClassNotFound(msg.RegistryClassId) - } + // If a registry class is referenced, it must exist and match this entry's asset class. + if err := k.validateRegistryClassForEntry(ctx, msg.RegistryClassId, msg.Key.AssetClassId); err != nil { + return nil, err } // Store the registry in state. @@ -269,15 +263,9 @@ func (k msgServer) RegistryBulkUpdate(ctx context.Context, msg *types.MsgRegistr } } - // If a registry class is referenced, it must exist. - if entry.RegistryClassId != "" { - has, err := k.HasRegistryClass(ctx, entry.RegistryClassId) - if err != nil { - return nil, fmt.Errorf("[%d]: %w", i, err) - } - if !has { - return nil, fmt.Errorf("[%d]: %w", i, types.NewErrCodeRegistryClassNotFound(entry.RegistryClassId)) - } + // If a registry class is referenced, it must exist and match this entry's asset class. + if err := k.validateRegistryClassForEntry(ctx, entry.RegistryClassId, entry.Key.AssetClassId); err != nil { + return nil, fmt.Errorf("[%d]: %w", i, err) } // Get the original registry, so we know what we're updating. diff --git a/x/registry/keeper/registry_class_acceptance_test.go b/x/registry/keeper/registry_class_acceptance_test.go index df89248269..fc03448af1 100644 --- a/x/registry/keeper/registry_class_acceptance_test.go +++ b/x/registry/keeper/registry_class_acceptance_test.go @@ -207,6 +207,26 @@ func (s *RegistryClassAcceptanceTestSuite) TestRegisterNFTWithRegistryClass() { s.Require().ErrorContains(err, "no-such-class") }) + s.Run("reject: registry class for a different asset class", func() { + _, err := s.msgServer.CreateRegistryClass(s.ctx, &types.MsgCreateRegistryClass{ + Signer: s.maintainer, + RegistryClassId: "class-other-asset", + AssetClassId: "some-other-asset-class", + Maintainer: s.maintainer, + RoleAuthorizations: []types.RoleAuthorization{servicerRoleAuthorization()}, + }) + s.Require().NoError(err) + + key := s.mintNFT("nft-mismatched-class") + _, err = s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ + Signer: s.nftOwner, + Key: key, + RegistryClassId: "class-other-asset", + }) + s.Require().Error(err) + s.Require().ErrorContains(err, "is for asset class \"some-other-asset-class\"") + }) + s.Run("success: entry stores registry class id", func() { key := s.mintNFT("nft-with-class") _, err := s.msgServer.RegisterNFT(s.ctx, &types.MsgRegisterNFT{ diff --git a/x/registry/types/errors.go b/x/registry/types/errors.go index 8b4a22ae5f..35983c7806 100644 --- a/x/registry/types/errors.go +++ b/x/registry/types/errors.go @@ -29,33 +29,35 @@ import ( type ErrCode string const ( - ErrCodeRegistryAlreadyExists ErrCode = "REGISTRY_ALREADY_EXISTS" - ErrCodeNFTNotFound ErrCode = "NFT_NOT_FOUND" - ErrCodeUnauthorized ErrCode = "UNAUTHORIZED" - ErrCodeInvalidRole ErrCode = "INVALID_ROLE" - ErrCodeRegistryNotFound ErrCode = "REGISTRY_NOT_FOUND" - ErrCodeAddressAlreadyHasRole ErrCode = "ADDRESS_ALREADY_HAS_ROLE" - ErrCodeInvalidKey ErrCode = "INVALID_KEY" - ErrCodeAddressDoesNotHaveRole ErrCode = "ADDRESS_DOES_NOT_HAVE_ROLE" - ErrCodeInvalidField ErrCode = "INVALID_FIELD" - ErrCodePendingChangeNotFound ErrCode = "PENDING_CHANGE_NOT_FOUND" - ErrCodeRegistryClassExists ErrCode = "REGISTRY_CLASS_ALREADY_EXISTS" - ErrCodeRegistryClassNotFound ErrCode = "REGISTRY_CLASS_NOT_FOUND" + ErrCodeRegistryAlreadyExists ErrCode = "REGISTRY_ALREADY_EXISTS" + ErrCodeNFTNotFound ErrCode = "NFT_NOT_FOUND" + ErrCodeUnauthorized ErrCode = "UNAUTHORIZED" + ErrCodeInvalidRole ErrCode = "INVALID_ROLE" + ErrCodeRegistryNotFound ErrCode = "REGISTRY_NOT_FOUND" + ErrCodeAddressAlreadyHasRole ErrCode = "ADDRESS_ALREADY_HAS_ROLE" + ErrCodeInvalidKey ErrCode = "INVALID_KEY" + ErrCodeAddressDoesNotHaveRole ErrCode = "ADDRESS_DOES_NOT_HAVE_ROLE" + ErrCodeInvalidField ErrCode = "INVALID_FIELD" + ErrCodePendingChangeNotFound ErrCode = "PENDING_CHANGE_NOT_FOUND" + ErrCodeRegistryClassExists ErrCode = "REGISTRY_CLASS_ALREADY_EXISTS" + ErrCodeRegistryClassNotFound ErrCode = "REGISTRY_CLASS_NOT_FOUND" + ErrCodeRegistryClassAssetMismatch ErrCode = "REGISTRY_CLASS_ASSET_MISMATCH" ) var ( - ErrRegistryAlreadyExists = cerrs.Register(ModuleName, 1, "registry already exists") - ErrNFTNotFound = cerrs.Register(ModuleName, 2, "NFT does not exist") - ErrUnauthorized = cerrs.Register(ModuleName, 3, "unauthorized") - ErrInvalidRole = cerrs.Register(ModuleName, 4, "invalid role") - ErrRegistryNotFound = cerrs.Register(ModuleName, 5, "registry not found") - ErrAddressAlreadyHasRole = cerrs.Register(ModuleName, 6, "address already has role") - ErrInvalidKey = cerrs.Register(ModuleName, 7, "invalid key") - ErrAddressDoesNotHaveRole = cerrs.Register(ModuleName, 8, "address does not have role") - ErrInvalidField = cerrs.Register(ModuleName, 9, "invalid field") - ErrPendingChangeNotFound = cerrs.Register(ModuleName, 10, "pending role change not found") - ErrRegistryClassExists = cerrs.Register(ModuleName, 11, "registry class already exists") - ErrRegistryClassNotFound = cerrs.Register(ModuleName, 12, "registry class not found") + ErrRegistryAlreadyExists = cerrs.Register(ModuleName, 1, "registry already exists") + ErrNFTNotFound = cerrs.Register(ModuleName, 2, "NFT does not exist") + ErrUnauthorized = cerrs.Register(ModuleName, 3, "unauthorized") + ErrInvalidRole = cerrs.Register(ModuleName, 4, "invalid role") + ErrRegistryNotFound = cerrs.Register(ModuleName, 5, "registry not found") + ErrAddressAlreadyHasRole = cerrs.Register(ModuleName, 6, "address already has role") + ErrInvalidKey = cerrs.Register(ModuleName, 7, "invalid key") + ErrAddressDoesNotHaveRole = cerrs.Register(ModuleName, 8, "address does not have role") + ErrInvalidField = cerrs.Register(ModuleName, 9, "invalid field") + ErrPendingChangeNotFound = cerrs.Register(ModuleName, 10, "pending role change not found") + ErrRegistryClassExists = cerrs.Register(ModuleName, 11, "registry class already exists") + ErrRegistryClassNotFound = cerrs.Register(ModuleName, 12, "registry class not found") + ErrRegistryClassAssetMismatch = cerrs.Register(ModuleName, 13, "registry class asset class mismatch") ) func NewErrCodeRegistryAlreadyExists(key string) error { @@ -101,3 +103,13 @@ func NewErrCodeRegistryClassExists(registryClassID string) error { func NewErrCodeRegistryClassNotFound(registryClassID string) error { return cerrs.Wrapf(ErrRegistryClassNotFound, "registry class not found: %q", registryClassID) } + +// NewErrCodeRegistryClassAssetMismatch reports that a registry entry references a registry class +// whose asset class id does not match the entry's asset class id. A registry class only governs +// entries within its own asset class, so applying it across collections would resolve authorization +// against the wrong policy tier. +func NewErrCodeRegistryClassAssetMismatch(registryClassID, classAssetClassID, entryAssetClassID string) error { + return cerrs.Wrapf(ErrRegistryClassAssetMismatch, + "registry class %q is for asset class %q, not %q", + registryClassID, classAssetClassID, entryAssetClassID) +} diff --git a/x/registry/types/genesis.go b/x/registry/types/genesis.go index 4ec4d68f93..abb9806b27 100644 --- a/x/registry/types/genesis.go +++ b/x/registry/types/genesis.go @@ -11,15 +11,15 @@ func DefaultGenesis() *GenesisState { // Validate validates the GenesisState. func (m *GenesisState) Validate() error { - seenClasses := make(map[string]bool, len(m.RegistryClasses)) + seenClasses := make(map[string]string, len(m.RegistryClasses)) for _, class := range m.RegistryClasses { if err := class.Validate(); err != nil { return fmt.Errorf("registry class: %w", err) } - if seenClasses[class.RegistryClassId] { + if _, ok := seenClasses[class.RegistryClassId]; ok { return fmt.Errorf("duplicate registry class id: %q", class.RegistryClassId) } - seenClasses[class.RegistryClassId] = true + seenClasses[class.RegistryClassId] = class.AssetClassId } for _, entry := range m.Entries { @@ -28,8 +28,17 @@ func (m *GenesisState) Validate() error { } // An entry that references a registry class must reference one that exists in genesis; // otherwise its authorization tier would silently fall back to params/legacy at runtime. - if entry.RegistryClassId != "" && !seenClasses[entry.RegistryClassId] { - return fmt.Errorf("entry %q references unknown registry class id: %q", entry.Key.String(), entry.RegistryClassId) + // The referenced class must also be for the same asset class as the entry, since a class + // only governs entries within its own asset class. + if entry.RegistryClassId != "" { + classAssetClassID, ok := seenClasses[entry.RegistryClassId] + if !ok { + return fmt.Errorf("entry %q references unknown registry class id: %q", entry.Key.String(), entry.RegistryClassId) + } + if classAssetClassID != entry.Key.AssetClassId { + return fmt.Errorf("entry %q: %w", entry.Key.String(), + NewErrCodeRegistryClassAssetMismatch(entry.RegistryClassId, classAssetClassID, entry.Key.AssetClassId)) + } } } diff --git a/x/registry/types/genesis_test.go b/x/registry/types/genesis_test.go index 5c05221e7e..d223f3b8fc 100644 --- a/x/registry/types/genesis_test.go +++ b/x/registry/types/genesis_test.go @@ -376,6 +376,34 @@ func TestGenesisState_Validate(t *testing.T) { }, expErr: "references unknown registry class id", }, + { + name: "invalid entry - references class for different asset class", + genesis: &types.GenesisState{ + RegistryClasses: []types.RegistryClass{ + { + RegistryClassId: "class-a", + AssetClassId: "class1", + Maintainer: validAddr, + }, + }, + Entries: []types.RegistryEntry{ + { + Key: &types.RegistryKey{ + AssetClassId: "class2", + NftId: "nft1", + }, + RegistryClassId: "class-a", + Roles: []types.RolesEntry{ + { + Role: types.RegistryRole_REGISTRY_ROLE_ORIGINATOR, + Addresses: []string{validAddr}, + }, + }, + }, + }, + }, + expErr: "registry class \"class-a\" is for asset class \"class1\", not \"class2\"", + }, } for _, tc := range tests { From d2175d9935bddf43562b0709a1c8378f3ddebd2b Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 13:53:44 -0600 Subject: [PATCH 27/36] fix(registry): expose ValidateRoleChange over POST and fix CLI JSON example The ValidateRoleChange REST endpoint was annotated as GET with path params, but the request carries role_updates and approvers lists that cannot be supplied via a query string (and the server rejects empty role_updates), making the REST route unusable. Switch it to POST with body "*" so clients can submit the full request message as JSON. Also make the create-registry-class help JSON example use consistent snake_case proto field names (registry_class_id) to match the generated example fixture. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/query.proto | 8 +- x/registry/client/cli/tx.go | 2 +- x/registry/types/query.pb.go | 140 +++++++++++------------ x/registry/types/query.pb.gw.go | 82 ++----------- 4 files changed, 88 insertions(+), 144 deletions(-) diff --git a/proto/provenance/registry/v1/query.proto b/proto/provenance/registry/v1/query.proto index 100e4b3c24..7c542228d4 100644 --- a/proto/provenance/registry/v1/query.proto +++ b/proto/provenance/registry/v1/query.proto @@ -63,8 +63,12 @@ service Query { // supplied approvers would satisfy every affected role's authorization policy without writing // any state. rpc ValidateRoleChange(QueryValidateRoleChangeRequest) returns (QueryValidateRoleChangeResponse) { - option (google.api.http).get = - "/provenance/registry/v1/validate_role_change/{key.asset_class_id}/{key.nft_id}"; + // This is a read-only query, but it is exposed over POST because the request carries the full + // role_updates and approvers lists, which cannot be supplied via a GET query string. + option (google.api.http) = { + post: "/provenance/registry/v1/validate_role_change" + body: "*" + }; } } diff --git a/x/registry/client/cli/tx.go b/x/registry/client/cli/tx.go index e0b7a7ee67..4eb485bf41 100644 --- a/x/registry/client/cli/tx.go +++ b/x/registry/client/cli/tx.go @@ -307,7 +307,7 @@ func CmdCreateRegistryClass() *cobra.Command { The file is a proto-JSON RegistryClass, e.g.: { - "registryClassId": "loan-registry-v1", + "registry_class_id": "loan-registry-v1", "asset_class_id": "loan.asset", "maintainer": "pb1...", "role_authorizations": [ { "role": "REGISTRY_ROLE_CONTROLLER", "authorizations": [ ... ] } ] diff --git a/x/registry/types/query.pb.go b/x/registry/types/query.pb.go index a0df21dbbe..8846babdba 100644 --- a/x/registry/types/query.pb.go +++ b/x/registry/types/query.pb.go @@ -983,77 +983,77 @@ func init() { } var fileDescriptor_c166c561e401a2eb = []byte{ - // 1119 bytes of a gzipped FileDescriptorProto + // 1117 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xcf, 0x24, 0x6d, 0xfe, 0xbc, 0x34, 0x29, 0x99, 0x5a, 0xe0, 0x98, 0xb0, 0xb5, 0xa6, 0x7f, - 0x30, 0x6e, 0xd9, 0x8d, 0xdd, 0x14, 0x2a, 0xd4, 0x53, 0x0a, 0x84, 0x12, 0x09, 0x99, 0xad, 0x1a, - 0x24, 0x38, 0x58, 0x13, 0x7b, 0xb0, 0x57, 0x71, 0x76, 0xb6, 0x3b, 0x6b, 0x0b, 0x63, 0xe5, 0x00, - 0xe2, 0xd2, 0x1b, 0x12, 0x1f, 0x80, 0x7e, 0x02, 0x0e, 0x1c, 0x01, 0x09, 0xc1, 0xa9, 0x17, 0xa4, - 0x4a, 0x5c, 0xb8, 0x80, 0x50, 0xc2, 0x07, 0x41, 0x3b, 0x3b, 0x6b, 0xef, 0xda, 0xde, 0xac, 0xdd, - 0xe6, 0xe6, 0x9d, 0x79, 0xbf, 0x37, 0xbf, 0xdf, 0x7b, 0x6f, 0xde, 0x1b, 0x03, 0x71, 0x5c, 0xde, - 0x61, 0x36, 0xb5, 0x6b, 0xcc, 0x70, 0x59, 0xc3, 0x12, 0x9e, 0xdb, 0x35, 0x3a, 0x25, 0xe3, 0x51, - 0x9b, 0xb9, 0x5d, 0xdd, 0x71, 0xb9, 0xc7, 0xf1, 0xcb, 0x03, 0x1b, 0x3d, 0xb4, 0xd1, 0x3b, 0xa5, - 0x5c, 0xb1, 0xc6, 0xc5, 0x21, 0x17, 0xc6, 0x3e, 0x15, 0x2c, 0x00, 0x18, 0x9d, 0xd2, 0x3e, 0xf3, - 0x68, 0xc9, 0x70, 0x68, 0xc3, 0xb2, 0xa9, 0x67, 0x71, 0x3b, 0xf0, 0x91, 0xcb, 0x34, 0x78, 0x83, - 0xcb, 0x9f, 0x86, 0xff, 0x4b, 0xad, 0x6e, 0x34, 0x38, 0x6f, 0xb4, 0x98, 0x41, 0x1d, 0xcb, 0xa0, - 0xb6, 0xcd, 0x3d, 0x09, 0x11, 0x6a, 0xf7, 0x5a, 0x02, 0xb7, 0x3e, 0x87, 0xc0, 0xac, 0x98, 0x60, - 0x46, 0xdb, 0x5e, 0x93, 0xbb, 0xd6, 0x97, 0x51, 0x1a, 0x57, 0x12, 0x6c, 0x1d, 0xea, 0xd2, 0x43, - 0x75, 0x2e, 0xa9, 0xc0, 0x2b, 0x1f, 0xfb, 0x6a, 0x76, 0x98, 0x67, 0x2a, 0x1b, 0x93, 0x3d, 0x6a, - 0x33, 0xe1, 0xe1, 0xdb, 0x30, 0x77, 0xc0, 0xba, 0x59, 0x94, 0x47, 0x85, 0xe5, 0xf2, 0x15, 0x7d, - 0x7c, 0x60, 0xf4, 0x10, 0xb5, 0xcb, 0xba, 0xa6, 0x6f, 0x4f, 0x6a, 0x90, 0x1d, 0xf5, 0x28, 0x1c, - 0x6e, 0x0b, 0x86, 0x77, 0x60, 0x31, 0xc4, 0x2a, 0xbf, 0xd7, 0xd2, 0xfc, 0xbe, 0x67, 0x7b, 0x6e, - 0x77, 0xfb, 0xdc, 0xd3, 0x7f, 0x2e, 0xcf, 0x98, 0x7d, 0x30, 0x79, 0x8c, 0x60, 0x7d, 0xe8, 0x14, - 0x8b, 0x89, 0x90, 0xf9, 0x55, 0x58, 0xa5, 0x42, 0x30, 0xaf, 0x5a, 0x6b, 0x51, 0x21, 0xaa, 0x56, - 0x5d, 0x1e, 0xb6, 0x64, 0x5e, 0x90, 0xab, 0xf7, 0xfc, 0xc5, 0xfb, 0x75, 0xfc, 0x3e, 0xc0, 0x20, - 0x75, 0xd9, 0x9a, 0xa4, 0x73, 0x5d, 0x0f, 0xf2, 0xac, 0xfb, 0x79, 0xd6, 0x83, 0xc2, 0x50, 0x79, - 0xd6, 0x2b, 0xb4, 0xc1, 0xd4, 0x09, 0x66, 0x04, 0x49, 0x7e, 0x44, 0x90, 0x1b, 0xc7, 0x45, 0x69, - 0xde, 0x05, 0x70, 0xfb, 0xab, 0x59, 0x94, 0x9f, 0x9b, 0x56, 0x75, 0x04, 0x8e, 0x77, 0xc6, 0x70, - 0x7e, 0x3d, 0x95, 0x73, 0xc0, 0x24, 0x46, 0xfa, 0x09, 0x82, 0x4b, 0x92, 0xf4, 0x07, 0x54, 0x98, - 0xbc, 0xc5, 0x5e, 0x2c, 0xe9, 0x38, 0x0b, 0x0b, 0xb4, 0x5e, 0x77, 0x99, 0x10, 0xd9, 0x59, 0x19, - 0xea, 0xf0, 0x13, 0xdf, 0x81, 0x73, 0x2e, 0x6f, 0xb1, 0xec, 0x5c, 0x1e, 0x15, 0x56, 0xcb, 0x57, - 0xd3, 0x3c, 0x4a, 0x2e, 0x12, 0x41, 0x4a, 0x90, 0x89, 0x33, 0x54, 0x01, 0x5d, 0x87, 0xc5, 0x26, - 0x15, 0x55, 0xe9, 0xd5, 0xe7, 0xb9, 0x68, 0x2e, 0x34, 0x03, 0x13, 0x62, 0xc0, 0x6b, 0x12, 0x52, - 0x61, 0x76, 0xdd, 0xb2, 0x1b, 0xfe, 0xda, 0xbd, 0x26, 0xb5, 0xfb, 0x79, 0xc3, 0xab, 0x30, 0xdb, - 0xaf, 0x86, 0x59, 0xab, 0x4e, 0xbe, 0x42, 0xa0, 0x25, 0x21, 0xd4, 0x71, 0x55, 0xb8, 0xe4, 0x04, - 0x9b, 0xf2, 0xc8, 0x6a, 0x4d, 0x6e, 0xab, 0x08, 0xbd, 0x91, 0xa4, 0x67, 0xc4, 0x9f, 0x4a, 0xe6, - 0x9a, 0x33, 0xbc, 0x41, 0xbe, 0x4f, 0xe4, 0x20, 0x5e, 0x30, 0x2b, 0x67, 0x55, 0xe1, 0x7f, 0x20, - 0xb8, 0x9c, 0xc8, 0x50, 0x85, 0x89, 0x42, 0x66, 0x4c, 0x98, 0xc2, 0x82, 0x9f, 0x3a, 0x4e, 0x78, - 0x24, 0x4e, 0x67, 0x58, 0xfc, 0x3b, 0xaa, 0x79, 0x84, 0x01, 0x93, 0x1d, 0x21, 0x8c, 0x75, 0x11, - 0xd6, 0x42, 0x82, 0xc3, 0xfd, 0xe3, 0xa2, 0x1b, 0x05, 0xdc, 0xaf, 0x13, 0x47, 0xdd, 0xfc, 0x21, - 0x47, 0x2a, 0x24, 0x26, 0xac, 0xc6, 0x3d, 0x4d, 0xda, 0xf3, 0xa4, 0x1b, 0x15, 0x88, 0x95, 0xd8, - 0x99, 0x84, 0xc1, 0xab, 0xa3, 0x27, 0x0e, 0x0a, 0xe5, 0xac, 0x32, 0xfe, 0x2b, 0x82, 0x8d, 0xf1, - 0xe7, 0x28, 0x6d, 0x7b, 0xf0, 0x52, 0x5c, 0xdb, 0xe4, 0xbd, 0x2d, 0xaa, 0x2e, 0x1e, 0xd1, 0xb3, - 0xcc, 0x71, 0x06, 0x70, 0x50, 0xb2, 0x72, 0xda, 0x29, 0x8d, 0xe4, 0x81, 0xea, 0x7a, 0xe1, 0xaa, - 0x52, 0x73, 0x17, 0xe6, 0x83, 0xa9, 0xa8, 0x32, 0xa4, 0x25, 0x96, 0xab, 0xb4, 0x52, 0xe4, 0x15, - 0x86, 0xfc, 0x1e, 0x5e, 0xe0, 0x3d, 0xda, 0xb2, 0xea, 0xd4, 0x63, 0xa3, 0x7d, 0xe7, 0x39, 0x2f, - 0xf0, 0x2e, 0x5c, 0x90, 0x97, 0xa9, 0xed, 0xf8, 0x6e, 0xfd, 0xde, 0xea, 0x47, 0x98, 0x24, 0xe2, - 0x79, 0x8b, 0x3d, 0x94, 0xa6, 0x8a, 0xe1, 0xb2, 0xdb, 0x5f, 0x11, 0x78, 0x03, 0x96, 0xa8, 0x23, - 0x91, 0xae, 0xc8, 0xce, 0xe5, 0xe7, 0x0a, 0x4b, 0xe6, 0x60, 0x81, 0x7c, 0xa2, 0xae, 0xf8, 0x38, - 0x0d, 0x2a, 0x4a, 0x19, 0x38, 0xcf, 0x5c, 0x97, 0xbb, 0xea, 0x36, 0x04, 0x1f, 0x58, 0x03, 0x08, - 0x5f, 0x1f, 0xac, 0x2e, 0xbb, 0xff, 0xa2, 0x19, 0x59, 0x29, 0x7f, 0xb3, 0x02, 0xe7, 0xa5, 0x67, - 0xfc, 0x0b, 0x82, 0xe5, 0xc8, 0xab, 0x00, 0x1b, 0x49, 0x3a, 0x12, 0x5e, 0x24, 0xb9, 0xcd, 0xc9, - 0x01, 0x01, 0x65, 0xf2, 0xe1, 0xd7, 0x7f, 0xfe, 0xf7, 0xdd, 0xec, 0xbb, 0x78, 0xdb, 0x48, 0x79, - 0x5f, 0x19, 0xbd, 0x03, 0xd6, 0xd5, 0xe3, 0xaf, 0x86, 0xa3, 0x60, 0xd1, 0xfe, 0xdc, 0xf3, 0x3f, - 0xf0, 0x13, 0x04, 0x2b, 0xb1, 0x11, 0x8f, 0x4b, 0x13, 0xf2, 0x19, 0x3c, 0x4d, 0x72, 0xe5, 0x69, - 0x20, 0x4a, 0x44, 0x41, 0x8a, 0x20, 0x38, 0x9f, 0x26, 0x02, 0xff, 0x86, 0x60, 0x41, 0x8d, 0x4b, - 0x7c, 0xe3, 0xd4, 0x93, 0xe2, 0x63, 0x3f, 0x77, 0x73, 0x32, 0x63, 0x45, 0xe8, 0x33, 0x49, 0xe8, - 0x21, 0x7e, 0x90, 0x44, 0x28, 0x9c, 0xcf, 0xe9, 0x51, 0x35, 0x7a, 0xea, 0xa1, 0x70, 0x64, 0xf4, - 0x7c, 0xc4, 0x91, 0x5f, 0x25, 0x6b, 0x23, 0x53, 0x01, 0xdf, 0x3e, 0x95, 0x60, 0xd2, 0xbc, 0xcf, - 0xbd, 0x35, 0x2d, 0x4c, 0x29, 0xbc, 0x23, 0x15, 0x96, 0xf1, 0x66, 0x92, 0xc2, 0x31, 0xb3, 0xce, - 0xe8, 0xf9, 0x55, 0xf2, 0x33, 0x02, 0x3c, 0x3a, 0x26, 0xf1, 0x94, 0x44, 0xfa, 0xf5, 0xf2, 0xf6, - 0xd4, 0x38, 0xa5, 0x60, 0x4b, 0x2a, 0xd0, 0xf1, 0xcd, 0x29, 0x14, 0x08, 0xfc, 0x13, 0x82, 0x95, - 0x58, 0x9f, 0x4e, 0xa9, 0xf1, 0x71, 0x13, 0x34, 0xa5, 0xc6, 0xc7, 0xce, 0x4a, 0xb2, 0x2d, 0xe9, - 0xde, 0xc5, 0xef, 0xa4, 0xd5, 0x78, 0x50, 0x47, 0x46, 0x6f, 0x64, 0x46, 0x1f, 0xe1, 0x1f, 0x10, - 0x5c, 0x1c, 0x9a, 0x57, 0xf8, 0xd6, 0xe4, 0x5c, 0x06, 0x41, 0xdf, 0x9a, 0x0e, 0xa4, 0x24, 0x6c, - 0x4a, 0x09, 0x45, 0x5c, 0x98, 0x4c, 0x02, 0x13, 0xf8, 0x31, 0x82, 0xf9, 0x60, 0xa2, 0xe0, 0xe2, - 0xe9, 0x79, 0x8e, 0x0e, 0xb1, 0xdc, 0x8d, 0x89, 0x6c, 0x15, 0xab, 0xeb, 0x92, 0x55, 0x1e, 0x6b, - 0xc6, 0xa9, 0x7f, 0x07, 0xf1, 0xdf, 0x08, 0xf0, 0x68, 0xef, 0x4f, 0xa9, 0xdb, 0xc4, 0x81, 0x97, - 0x52, 0xb7, 0xc9, 0x43, 0x86, 0xec, 0x49, 0xbe, 0x15, 0xfc, 0x51, 0x12, 0xdf, 0x8e, 0xc2, 0xc6, - 0xaf, 0x5e, 0x5a, 0x9f, 0xd9, 0x3e, 0x78, 0x7a, 0xac, 0xa1, 0x67, 0xc7, 0x1a, 0xfa, 0xf7, 0x58, - 0x43, 0xdf, 0x9e, 0x68, 0x33, 0xcf, 0x4e, 0xb4, 0x99, 0xbf, 0x4e, 0xb4, 0x19, 0x58, 0xb7, 0x78, - 0x02, 0xd9, 0x0a, 0xfa, 0x74, 0xab, 0x61, 0x79, 0xcd, 0xf6, 0xbe, 0x5e, 0xe3, 0x87, 0x11, 0x42, - 0x6f, 0x5a, 0x3c, 0x4a, 0xef, 0x8b, 0x01, 0x41, 0xaf, 0xeb, 0x30, 0xb1, 0x3f, 0x2f, 0xff, 0x5c, - 0xdf, 0xfa, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x53, 0x45, 0x14, 0x20, 0x72, 0x10, 0x00, 0x00, + 0x14, 0xcf, 0xc4, 0x6d, 0xfe, 0xbc, 0xfc, 0x29, 0x99, 0x5a, 0xe0, 0x98, 0xb0, 0xb5, 0xa6, 0x7f, + 0x30, 0x6e, 0xd9, 0x8d, 0xdd, 0x94, 0x56, 0x55, 0x4f, 0x29, 0x10, 0x4a, 0x2e, 0x61, 0xab, 0x16, + 0x09, 0x0e, 0xd6, 0xc4, 0x1e, 0xec, 0x55, 0x9c, 0x9d, 0xed, 0xce, 0xda, 0xc2, 0x58, 0x39, 0xc0, + 0xad, 0x37, 0x24, 0x3e, 0x00, 0xfd, 0x04, 0x1c, 0x38, 0x02, 0x02, 0xc1, 0xa9, 0x17, 0xa4, 0x4a, + 0x5c, 0x38, 0x21, 0x94, 0xc0, 0xf7, 0x40, 0x3b, 0x3b, 0x6b, 0xef, 0xda, 0xde, 0xac, 0x4d, 0x73, + 0xf3, 0xce, 0xbe, 0xdf, 0x7b, 0xbf, 0xdf, 0x7b, 0x6f, 0xdf, 0x1b, 0x03, 0x71, 0x5c, 0xde, 0x61, + 0x36, 0xb5, 0x6b, 0xcc, 0x70, 0x59, 0xc3, 0x12, 0x9e, 0xdb, 0x35, 0x3a, 0x65, 0xe3, 0x49, 0x9b, + 0xb9, 0x5d, 0xdd, 0x71, 0xb9, 0xc7, 0xf1, 0xab, 0x03, 0x1b, 0x3d, 0xb4, 0xd1, 0x3b, 0xe5, 0x7c, + 0xa9, 0xc6, 0xc5, 0x21, 0x17, 0xc6, 0x3e, 0x15, 0x2c, 0x00, 0x18, 0x9d, 0xf2, 0x3e, 0xf3, 0x68, + 0xd9, 0x70, 0x68, 0xc3, 0xb2, 0xa9, 0x67, 0x71, 0x3b, 0xf0, 0x91, 0xcf, 0x36, 0x78, 0x83, 0xcb, + 0x9f, 0x86, 0xff, 0x4b, 0x9d, 0x6e, 0x34, 0x38, 0x6f, 0xb4, 0x98, 0x41, 0x1d, 0xcb, 0xa0, 0xb6, + 0xcd, 0x3d, 0x09, 0x11, 0xea, 0xed, 0xd5, 0x04, 0x6e, 0x7d, 0x0e, 0x81, 0x59, 0x29, 0xc1, 0x8c, + 0xb6, 0xbd, 0x26, 0x77, 0xad, 0x2f, 0xa2, 0x34, 0x2e, 0x27, 0xd8, 0x3a, 0xd4, 0xa5, 0x87, 0x2a, + 0x2e, 0xd9, 0x83, 0xd7, 0x3e, 0xf2, 0xd5, 0xec, 0x30, 0xcf, 0x54, 0x36, 0x26, 0x7b, 0xd2, 0x66, + 0xc2, 0xc3, 0xb7, 0x20, 0x73, 0xc0, 0xba, 0x39, 0x54, 0x40, 0xc5, 0xa5, 0xca, 0x65, 0x7d, 0x7c, + 0x62, 0xf4, 0x10, 0xb5, 0xcb, 0xba, 0xa6, 0x6f, 0x4f, 0x6a, 0x90, 0x1b, 0xf5, 0x28, 0x1c, 0x6e, + 0x0b, 0x86, 0x77, 0x60, 0x21, 0xc4, 0x2a, 0xbf, 0x57, 0xd3, 0xfc, 0xbe, 0x67, 0x7b, 0x6e, 0x77, + 0xfb, 0xdc, 0xf3, 0xbf, 0x2e, 0xcd, 0x98, 0x7d, 0x30, 0x79, 0x8a, 0x60, 0x7d, 0x28, 0x8a, 0xc5, + 0x44, 0xc8, 0xfc, 0x0a, 0xac, 0x52, 0x21, 0x98, 0x57, 0xad, 0xb5, 0xa8, 0x10, 0x55, 0xab, 0x2e, + 0x83, 0x2d, 0x9a, 0xcb, 0xf2, 0xf4, 0xbe, 0x7f, 0xf8, 0xa0, 0x8e, 0xdf, 0x07, 0x18, 0x94, 0x2e, + 0x57, 0x93, 0x74, 0xae, 0xe9, 0x41, 0x9d, 0x75, 0xbf, 0xce, 0x7a, 0xd0, 0x18, 0xaa, 0xce, 0xfa, + 0x1e, 0x6d, 0x30, 0x15, 0xc1, 0x8c, 0x20, 0xc9, 0xf7, 0x08, 0xf2, 0xe3, 0xb8, 0x28, 0xcd, 0xbb, + 0x00, 0x6e, 0xff, 0x34, 0x87, 0x0a, 0x99, 0x69, 0x55, 0x47, 0xe0, 0x78, 0x67, 0x0c, 0xe7, 0x37, + 0x53, 0x39, 0x07, 0x4c, 0x62, 0xa4, 0x9f, 0x21, 0xb8, 0x28, 0x49, 0x7f, 0x40, 0x85, 0xc9, 0x5b, + 0xec, 0xe5, 0x8a, 0x8e, 0x73, 0x30, 0x4f, 0xeb, 0x75, 0x97, 0x09, 0x91, 0x9b, 0x95, 0xa9, 0x0e, + 0x1f, 0xf1, 0x1d, 0x38, 0xe7, 0xf2, 0x16, 0xcb, 0x65, 0x0a, 0xa8, 0xb8, 0x5a, 0xb9, 0x92, 0xe6, + 0x51, 0x72, 0x91, 0x08, 0x52, 0x86, 0x6c, 0x9c, 0xa1, 0x4a, 0xe8, 0x3a, 0x2c, 0x34, 0xa9, 0xa8, + 0x4a, 0xaf, 0x3e, 0xcf, 0x05, 0x73, 0xbe, 0x19, 0x98, 0x10, 0x03, 0xde, 0x90, 0x90, 0x3d, 0x66, + 0xd7, 0x2d, 0xbb, 0xe1, 0x9f, 0xdd, 0x6f, 0x52, 0xbb, 0x5f, 0x37, 0xbc, 0x0a, 0xb3, 0xfd, 0x6e, + 0x98, 0xb5, 0xea, 0xe4, 0x4b, 0x04, 0x5a, 0x12, 0x42, 0x85, 0xab, 0xc2, 0x45, 0x27, 0x78, 0x29, + 0x43, 0x56, 0x6b, 0xf2, 0xb5, 0xca, 0xd0, 0x5b, 0x49, 0x7a, 0x46, 0xfc, 0xa9, 0x62, 0xae, 0x39, + 0xc3, 0x2f, 0xc8, 0xb7, 0x89, 0x1c, 0xc4, 0x4b, 0x56, 0xe5, 0xac, 0x3a, 0xfc, 0x77, 0x04, 0x97, + 0x12, 0x19, 0xaa, 0x34, 0x51, 0xc8, 0x8e, 0x49, 0x53, 0xd8, 0xf0, 0x53, 0xe7, 0x09, 0x8f, 0xe4, + 0xe9, 0x0c, 0x9b, 0x7f, 0x47, 0x0d, 0x8f, 0x30, 0x61, 0x72, 0x22, 0x84, 0xb9, 0x2e, 0xc1, 0x5a, + 0x48, 0x70, 0x78, 0x7e, 0x5c, 0x70, 0xa3, 0x80, 0x07, 0x75, 0xe2, 0xa8, 0x2f, 0x7f, 0xc8, 0x91, + 0x4a, 0x89, 0x09, 0xab, 0x71, 0x4f, 0x93, 0xce, 0x3c, 0xe9, 0x46, 0x25, 0x62, 0x25, 0x16, 0x93, + 0x30, 0x78, 0x7d, 0x34, 0xe2, 0xa0, 0x51, 0xce, 0xaa, 0xe2, 0xbf, 0x20, 0xd8, 0x18, 0x1f, 0x47, + 0x69, 0x7b, 0x0c, 0xaf, 0xc4, 0xb5, 0x4d, 0x3e, 0xdb, 0xa2, 0xea, 0xe2, 0x19, 0x3d, 0xcb, 0x1a, + 0x67, 0x01, 0x07, 0x2d, 0x2b, 0xb7, 0x9d, 0xd2, 0x48, 0x1e, 0xaa, 0xa9, 0x17, 0x9e, 0x2a, 0x35, + 0xf7, 0x60, 0x2e, 0xd8, 0x8a, 0xaa, 0x42, 0x5a, 0x62, 0xbb, 0x4a, 0x2b, 0x45, 0x5e, 0x61, 0xc8, + 0x6f, 0xe1, 0x07, 0xfc, 0x98, 0xb6, 0xac, 0x3a, 0xf5, 0xd8, 0xe8, 0xdc, 0xf9, 0x9f, 0x1f, 0xf0, + 0x2e, 0x2c, 0xcb, 0x8f, 0xa9, 0xed, 0xf8, 0x6e, 0xfd, 0xd9, 0xea, 0x67, 0x98, 0x24, 0xe2, 0x79, + 0x8b, 0x3d, 0x92, 0xa6, 0x8a, 0xe1, 0x92, 0xdb, 0x3f, 0x11, 0x78, 0x03, 0x16, 0xa9, 0x23, 0x91, + 0xae, 0xc8, 0x65, 0x0a, 0x99, 0xe2, 0xa2, 0x39, 0x38, 0x20, 0x1f, 0xab, 0x4f, 0x7c, 0x9c, 0x06, + 0x95, 0xa5, 0x2c, 0x9c, 0x67, 0xae, 0xcb, 0x5d, 0xf5, 0x35, 0x04, 0x0f, 0x58, 0x03, 0x08, 0x6f, + 0x1f, 0xac, 0x2e, 0xa7, 0xff, 0x82, 0x19, 0x39, 0xa9, 0xfc, 0xbb, 0x0c, 0xe7, 0xa5, 0x67, 0xfc, + 0x13, 0x82, 0xa5, 0xc8, 0xad, 0x00, 0x1b, 0x49, 0x3a, 0x12, 0x6e, 0x24, 0xf9, 0xcd, 0xc9, 0x01, + 0x01, 0x65, 0xf2, 0xe1, 0x57, 0x7f, 0xfc, 0xf3, 0xcd, 0xec, 0xbb, 0x78, 0xdb, 0x48, 0xb9, 0x5f, + 0x19, 0xbd, 0x03, 0xd6, 0xd5, 0xe3, 0xb7, 0x86, 0xa3, 0xe0, 0xd0, 0xfe, 0xcc, 0xf3, 0x1f, 0xf0, + 0x33, 0x04, 0x2b, 0xb1, 0x15, 0x8f, 0xcb, 0x13, 0xf2, 0x19, 0x5c, 0x4d, 0xf2, 0x95, 0x69, 0x20, + 0x4a, 0x44, 0x51, 0x8a, 0x20, 0xb8, 0x90, 0x26, 0x02, 0xff, 0x8a, 0x60, 0x5e, 0xad, 0x4b, 0x7c, + 0xfd, 0xd4, 0x48, 0xf1, 0xb5, 0x9f, 0xbf, 0x31, 0x99, 0xb1, 0x22, 0xf4, 0xa9, 0x24, 0xf4, 0x08, + 0x3f, 0x4c, 0x22, 0x14, 0xee, 0xe7, 0xf4, 0xac, 0x1a, 0x3d, 0x75, 0x51, 0x38, 0x32, 0x7a, 0x3e, + 0xe2, 0xc8, 0xef, 0x92, 0xb5, 0x91, 0xad, 0x80, 0x6f, 0x9d, 0x4a, 0x30, 0x69, 0xdf, 0xe7, 0xdf, + 0x99, 0x16, 0xa6, 0x14, 0xde, 0x91, 0x0a, 0x2b, 0x78, 0x33, 0x49, 0xe1, 0x98, 0x5d, 0x67, 0xf4, + 0xfc, 0x2e, 0xf9, 0x11, 0x01, 0x1e, 0x5d, 0x93, 0x78, 0x4a, 0x22, 0xfd, 0x7e, 0xb9, 0x3d, 0x35, + 0x4e, 0x29, 0xd8, 0x92, 0x0a, 0x74, 0x7c, 0x63, 0x0a, 0x05, 0x02, 0xff, 0x80, 0x60, 0x25, 0x36, + 0xa7, 0x53, 0x7a, 0x7c, 0xdc, 0x06, 0x4d, 0xe9, 0xf1, 0xb1, 0xbb, 0x92, 0x6c, 0x4b, 0xba, 0xf7, + 0xf0, 0xdd, 0xb4, 0x1e, 0x0f, 0xfa, 0xc8, 0xe8, 0x8d, 0xec, 0xe8, 0x23, 0xfc, 0x1d, 0x82, 0x0b, + 0x43, 0xfb, 0x0a, 0xdf, 0x9c, 0x9c, 0xcb, 0x20, 0xe9, 0x5b, 0xd3, 0x81, 0x94, 0x84, 0x4d, 0x29, + 0xa1, 0x84, 0x8b, 0x93, 0x49, 0x60, 0x02, 0x3f, 0x45, 0x30, 0x17, 0x6c, 0x14, 0x5c, 0x3a, 0xbd, + 0xce, 0xd1, 0x25, 0x96, 0xbf, 0x3e, 0x91, 0xad, 0x62, 0x75, 0x4d, 0xb2, 0x2a, 0x60, 0xcd, 0x38, + 0xf5, 0xef, 0x20, 0xfe, 0x19, 0x01, 0x1e, 0x9d, 0xfd, 0x29, 0x7d, 0x9b, 0xb8, 0xf0, 0x52, 0xfa, + 0x36, 0x79, 0xc9, 0x90, 0xdb, 0x92, 0x6f, 0xf9, 0x2e, 0x2a, 0x91, 0xc4, 0xd6, 0xed, 0x28, 0x78, + 0xb4, 0x77, 0xb7, 0x0f, 0x9e, 0x1f, 0x6b, 0xe8, 0xc5, 0xb1, 0x86, 0xfe, 0x3e, 0xd6, 0xd0, 0xd7, + 0x27, 0xda, 0xcc, 0x8b, 0x13, 0x6d, 0xe6, 0xcf, 0x13, 0x6d, 0x06, 0xd6, 0x2d, 0x9e, 0xc0, 0x66, + 0x0f, 0x7d, 0xb2, 0xd5, 0xb0, 0xbc, 0x66, 0x7b, 0x5f, 0xaf, 0xf1, 0xc3, 0x48, 0xb8, 0xb7, 0x2d, + 0x1e, 0x0d, 0xfe, 0xf9, 0x20, 0xbc, 0xd7, 0x75, 0x98, 0xd8, 0x9f, 0x93, 0xff, 0x9e, 0x6f, 0xfe, + 0x17, 0x00, 0x00, 0xff, 0xff, 0xb8, 0x46, 0x55, 0xb3, 0x53, 0x10, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/x/registry/types/query.pb.gw.go b/x/registry/types/query.pb.gw.go index d510c09c8a..6da28ace7f 100644 --- a/x/registry/types/query.pb.gw.go +++ b/x/registry/types/query.pb.gw.go @@ -505,47 +505,15 @@ func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshal } -var ( - filter_Query_ValidateRoleChange_0 = &utilities.DoubleArray{Encoding: map[string]int{"key": 0, "asset_class_id": 1, "nft_id": 2}, Base: []int{1, 1, 1, 2, 0, 0}, Check: []int{0, 1, 2, 2, 3, 4}} -) - func request_Query_ValidateRoleChange_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq QueryValidateRoleChangeRequest var metadata runtime.ServerMetadata - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["key.asset_class_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.asset_class_id") + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) } - - err = runtime.PopulateFieldFromPath(&protoReq, "key.asset_class_id", val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.asset_class_id", err) - } - - val, ok = pathParams["key.nft_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.nft_id") - } - - err = runtime.PopulateFieldFromPath(&protoReq, "key.nft_id", val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.nft_id", err) - } - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) - } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ValidateRoleChange_0); err != nil { + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -558,39 +526,11 @@ func local_request_Query_ValidateRoleChange_0(ctx context.Context, marshaler run var protoReq QueryValidateRoleChangeRequest var metadata runtime.ServerMetadata - var ( - val string - ok bool - err error - _ = err - ) - - val, ok = pathParams["key.asset_class_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.asset_class_id") - } - - err = runtime.PopulateFieldFromPath(&protoReq, "key.asset_class_id", val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.asset_class_id", err) - } - - val, ok = pathParams["key.nft_id"] - if !ok { - return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "key.nft_id") - } - - err = runtime.PopulateFieldFromPath(&protoReq, "key.nft_id", val) - - if err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "key.nft_id", err) - } - - if err := req.ParseForm(); err != nil { - return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) } - if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_ValidateRoleChange_0); err != nil { + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -789,7 +729,7 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_ValidateRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_Query_ValidateRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() var stream runtime.ServerTransportStream @@ -1013,7 +953,7 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_ValidateRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("POST", pattern_Query_ValidateRoleChange_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -1053,7 +993,7 @@ var ( pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) - pattern_Query_ValidateRoleChange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"provenance", "registry", "v1", "validate_role_change", "key.asset_class_id", "key.nft_id"}, "", runtime.AssumeColonVerbOpt(false))) + pattern_Query_ValidateRoleChange_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"provenance", "registry", "v1", "validate_role_change"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( From ecba3afcaae04f764be1daea3650ef57e8638281 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 16:34:40 -0600 Subject: [PATCH 28/36] fix(registry): fail closed on missing registry class in authorization roleAuthorizationsForEntry now panics when an entry references a registry class that is absent from state, instead of silently downgrading to the params/legacy authorization tier. CreateRegistry enforces the registry class invariant (exists and matches the entry's asset class) at the keeper layer and rejects a nil key, preventing invalid state from direct keeper callers that bypass the msg/genesis paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/class.go | 4 +++ x/registry/keeper/keeper.go | 12 ++++++++ x/registry/keeper/keeper_test.go | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go index cbaaacc835..25fe011b6e 100644 --- a/x/registry/keeper/class.go +++ b/x/registry/keeper/class.go @@ -149,6 +149,10 @@ func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.Reg } return types.RoleAuthorizationMapFrom(class.RoleAuthorizations) } + // The entry references a registry class that is not present in state. Falling back to + // params/legacy here would be an implicit fail-open authorization downgrade, so fail closed + // instead, consistent with how read errors and asset-class mismatches are handled above. + panic(types.NewErrCodeRegistryClassNotFound(entry.RegistryClassId)) } return k.GetParams(ctx).RoleAuthorizationMap() } diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 9fc0114e43..06c560d70c 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -124,6 +124,18 @@ func (k Keeper) CreateDefaultRegistry(ctx context.Context, ownerAddrStr string, // CreateRegistry stores a new registry entry in state. // Returns an error if the registry already exists, or if there's a problem. func (k Keeper) CreateRegistry(ctx context.Context, key *types.RegistryKey, roles []types.RolesEntry, registryClassID string) error { + if key == nil { + return fmt.Errorf("registry key must not be nil") + } + + // Defense in depth: enforce the registry class invariant at the keeper layer so invalid state + // cannot be introduced via direct keeper calls (other modules/tests) that bypass the msg and + // genesis validation paths. When set, the class must exist and be scoped to this entry's asset + // class; otherwise authorization would later resolve against the wrong policy tier. + if err := k.validateRegistryClassForEntry(ctx, registryClassID, key.AssetClassId); err != nil { + return err + } + // Already exists check has, err := k.Registry.Has(ctx, key.CollKey()) if err != nil { diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 9d03d6a08d..19f3f55ce5 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -132,6 +132,55 @@ func (s *KeeperTestSuite) TestCreateRegistry() { s.Require().EqualError(err, expDupErr) } +// TestCreateRegistry_RejectsInvalidClassReference verifies the keeper-layer invariant that an entry +// may only be created with a registry class id that exists and is scoped to the entry's asset class. +// This guards against invalid state introduced via direct keeper calls that bypass msg/genesis. +func (s *KeeperTestSuite) TestCreateRegistry_RejectsInvalidClassReference() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: "create-registry-bad-class-nft", + } + + // A nil key must be rejected rather than panic. + err := s.app.RegistryKeeper.CreateRegistry(s.ctx, nil, nil, "") + s.Require().EqualError(err, "registry key must not be nil") + + // Referencing a class that does not exist must fail closed. + err = s.app.RegistryKeeper.CreateRegistry(s.ctx, key, nil, "missing-class") + s.Require().ErrorIs(err, types.NewErrCodeRegistryClassNotFound("missing-class")) + + // The entry must not have been written. + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) + s.Require().NoError(err) + s.Require().Nil(entry) +} + +// TestRoleAuthorizationsForEntry_FailsClosedOnMissingClass verifies that resolving authorization for +// an entry whose registry class id is absent from state fails closed (panics) rather than silently +// downgrading to the params/legacy authorization tier. +func (s *KeeperTestSuite) TestRoleAuthorizationsForEntry_FailsClosedOnMissingClass() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: "fail-closed-missing-class-nft", + } + // Seed the entry directly to bypass CreateRegistry's validation, simulating corrupt state or a + // class that was removed after the entry referenced it. + s.Require().NoError(s.app.RegistryKeeper.SetRegistry(s.ctx, types.RegistryEntry{ + Key: key, + RegistryClassId: "missing-class", + })) + + msgServer := keeper.NewMsgServer(s.app.RegistryKeeper) + s.Require().Panics(func() { + _, _ = msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.user1, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.user1}, + }) + }) +} + func (s *KeeperTestSuite) TestGrantRole() { // Create a base registry for testing baseKey := &types.RegistryKey{ From cd404c8dcf46f11490a44405bd70cd0d10892c78 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Thu, 25 Jun 2026 17:17:45 -0600 Subject: [PATCH 29/36] PR feedback --- x/registry/keeper/class.go | 39 ++++++++++++--------- x/registry/keeper/keeper.go | 25 ++++++++----- x/registry/keeper/keeper_test.go | 18 +++++----- x/registry/keeper/msg_server.go | 42 +++++++++++++++++----- x/registry/keeper/pending.go | 58 ++++++++++++++++++++++--------- x/registry/keeper/query_server.go | 8 +---- x/registry/types/msgs.go | 31 +++++++++++++++++ x/registry/types/msgs_test.go | 34 ++++++++++++++++++ 8 files changed, 188 insertions(+), 67 deletions(-) diff --git a/x/registry/keeper/class.go b/x/registry/keeper/class.go index 25fe011b6e..ca60ff039a 100644 --- a/x/registry/keeper/class.go +++ b/x/registry/keeper/class.go @@ -132,27 +132,32 @@ func (k Keeper) GetRegistryClasses(ctx context.Context, pagination *query.PageRe // 2. Module params default (fallback): otherwise use the module's default policies (governance- // managed via MsgUpdateParams). Roles not covered by the returned map fall back to legacy // NFT-owner authorization at the call site. -func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.RegistryEntry) map[types.RegistryRole]types.RoleAuthorization { +// +// It returns a deterministic error (rather than panicking) when a referenced class cannot be +// resolved: a store read error, a class that is absent from state, or a class whose asset class +// does not match the entry. These conditions indicate inconsistent state and must fail closed, +// never silently downgrading to a weaker authorization tier, but they must also not halt block +// processing. +func (k Keeper) roleAuthorizationsForEntry(ctx context.Context, entry *types.RegistryEntry) (map[types.RegistryRole]types.RoleAuthorization, error) { if entry != nil && entry.RegistryClassId != "" { class, err := k.GetRegistryClass(ctx, entry.RegistryClassId) if err != nil { - // A store/read error must not silently downgrade to a weaker fallback policy. Fail fast - // so the authorization decision is never made against the wrong tier. - panic(fmt.Errorf("could not resolve registry class %q for authorization: %w", entry.RegistryClassId, err)) + // A store/read error must not silently downgrade to a weaker fallback policy. + return nil, fmt.Errorf("could not resolve registry class %q for authorization: %w", entry.RegistryClassId, err) + } + if class == nil { + // The entry references a registry class that is not present in state. Falling back to + // params/legacy here would be an implicit fail-open authorization downgrade, so fail + // closed instead. + return nil, types.NewErrCodeRegistryClassNotFound(entry.RegistryClassId) } - if class != nil { - // Defense in depth: enforcement at write time (register, bulk update, genesis) keeps the - // referenced class scoped to the entry's asset class. If a mismatched class is ever found - // in state, fail fast rather than resolve authorization against the wrong policy tier. - if entry.Key != nil && class.AssetClassId != entry.Key.AssetClassId { - panic(types.NewErrCodeRegistryClassAssetMismatch(entry.RegistryClassId, class.AssetClassId, entry.Key.AssetClassId)) - } - return types.RoleAuthorizationMapFrom(class.RoleAuthorizations) + // Defense in depth: enforcement at write time (register, bulk update, genesis) keeps the + // referenced class scoped to the entry's asset class. If a mismatched class is ever found in + // state, fail closed rather than resolve authorization against the wrong policy tier. + if entry.Key != nil && class.AssetClassId != entry.Key.AssetClassId { + return nil, types.NewErrCodeRegistryClassAssetMismatch(entry.RegistryClassId, class.AssetClassId, entry.Key.AssetClassId) } - // The entry references a registry class that is not present in state. Falling back to - // params/legacy here would be an implicit fail-open authorization downgrade, so fail closed - // instead, consistent with how read errors and asset-class mismatches are handled above. - panic(types.NewErrCodeRegistryClassNotFound(entry.RegistryClassId)) + return types.RoleAuthorizationMapFrom(class.RoleAuthorizations), nil } - return k.GetParams(ctx).RoleAuthorizationMap() + return k.GetParams(ctx).RoleAuthorizationMap(), nil } diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 06c560d70c..5345a4fab6 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -285,30 +285,36 @@ func (k Keeper) RevokeRole(ctx context.Context, key *types.RegistryKey, role typ // given role. For a policy-governed role it returns the satisfying authorization path's signers; for // a non-policy role it returns the NFT owner among the approvers (the legacy fallback). before is the // pre-mutation entry, newAddrs the addresses being assigned to the role, and approvers the signers. -func (k Keeper) roleChangeSigners(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, newAddrs, approvers []string) []types.RoleSigner { - roleAuths := k.roleAuthorizationsForEntry(ctx, before) +func (k Keeper) roleChangeSigners(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, newAddrs, approvers []string) ([]types.RoleSigner, error) { + roleAuths, err := k.roleAuthorizationsForEntry(ctx, before) + if err != nil { + return nil, err + } if roleAuth, ok := roleAuths[role]; ok { - return k.CollectSatisfyingSigners(ctx, roleAuth, before, newAddrs, approvers) + return k.CollectSatisfyingSigners(ctx, roleAuth, before, newAddrs, approvers), nil } for _, a := range approvers { if k.ValidateNFTOwner(ctx, &before.Key.AssetClassId, &before.Key.NftId, a) == nil { - return []types.RoleSigner{types.NewNFTOwnerSigner(a)} + return []types.RoleSigner{types.NewNFTOwnerSigner(a)}, nil } } - return nil + return nil, nil } // emitRoleUpdated emits the comprehensive EventRoleUpdated for a single role, reading the // post-mutation addresses from state. before is the pre-mutation entry (source of previous -// addresses and registry class id), and signers is the authorizing signer set. -func (k Keeper) emitRoleUpdated(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, signers []types.RoleSigner) { +// addresses and registry class id), and signers is the authorizing signer set. It returns a +// deterministic error (rather than panicking) if the post-mutation read-back fails, so an +// inconsistent store cannot halt block processing. +func (k Keeper) emitRoleUpdated(ctx context.Context, before *types.RegistryEntry, role types.RegistryRole, signers []types.RoleSigner) error { previous := before.GetRoleAddrs(role) var current []string after, err := k.GetRegistry(ctx, before.Key) if err != nil { // The mutation has already been committed; a read-back error means the store is - // inconsistent. Fail fast rather than emit a misleading audit event with empty addresses. - panic(fmt.Errorf("could not read back registry %q for EventRoleUpdated: %w", before.Key.String(), err)) + // inconsistent. Surface the error rather than emit a misleading audit event with empty + // addresses; returning it rolls back the transaction deterministically. + return fmt.Errorf("could not read back registry %q for EventRoleUpdated: %w", before.Key.String(), err) } // after may legitimately be nil if the role change removed the entry's last role and the entry // was cleaned up; in that case current is correctly empty. @@ -316,6 +322,7 @@ func (k Keeper) emitRoleUpdated(ctx context.Context, before *types.RegistryEntry current = after.GetRoleAddrs(role) } k.EmitEvent(ctx, types.NewEventRoleUpdated(before.Key, before.RegistryClassId, role, previous, current, signers)) + return nil } func (k Keeper) HasRole(ctx context.Context, key *types.RegistryKey, role types.RegistryRole, address string) (bool, error) { diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 19f3f55ce5..27cd7f0895 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -156,8 +156,9 @@ func (s *KeeperTestSuite) TestCreateRegistry_RejectsInvalidClassReference() { } // TestRoleAuthorizationsForEntry_FailsClosedOnMissingClass verifies that resolving authorization for -// an entry whose registry class id is absent from state fails closed (panics) rather than silently -// downgrading to the params/legacy authorization tier. +// an entry whose registry class id is absent from state fails closed: the role-change message +// returns a deterministic error (rather than panicking or silently downgrading to the params/legacy +// authorization tier), so an inconsistent store cannot halt block processing. func (s *KeeperTestSuite) TestRoleAuthorizationsForEntry_FailsClosedOnMissingClass() { key := &types.RegistryKey{ AssetClassId: s.validNFTClass.Id, @@ -171,14 +172,13 @@ func (s *KeeperTestSuite) TestRoleAuthorizationsForEntry_FailsClosedOnMissingCla })) msgServer := keeper.NewMsgServer(s.app.RegistryKeeper) - s.Require().Panics(func() { - _, _ = msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ - Signer: s.user1, - Key: key, - Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, - Addresses: []string{s.user1}, - }) + _, err := msgServer.GrantRole(s.ctx, &types.MsgGrantRole{ + Signer: s.user1, + Key: key, + Role: types.RegistryRole_REGISTRY_ROLE_SERVICER, + Addresses: []string{s.user1}, }) + s.Require().ErrorIs(err, types.NewErrCodeRegistryClassNotFound("missing-class")) } func (s *KeeperTestSuite) TestGrantRole() { diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 6293e49d07..e2b7285cb7 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -60,7 +60,10 @@ func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*typ return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) } - roleAuths := k.roleAuthorizationsForEntry(ctx, entry) + roleAuths, err := k.roleAuthorizationsForEntry(ctx, entry) + if err != nil { + return nil, err + } if roleAuth, ok := roleAuths[msg.Role]; ok { // Policy-based path: the incoming addresses are the "new" assignment. A single signer must // satisfy the policy on its own; multi-party changes go through ProposeRoleChange. @@ -80,8 +83,13 @@ func (k msgServer) GrantRole(ctx context.Context, msg *types.MsgGrantRole) (*typ return nil, err } - signers := k.roleChangeSigners(ctx, entry, msg.Role, msg.Addresses, []string{msg.Signer}) - k.emitRoleUpdated(ctx, entry, msg.Role, signers) + signers, err := k.roleChangeSigners(ctx, entry, msg.Role, msg.Addresses, []string{msg.Signer}) + if err != nil { + return nil, err + } + if err := k.emitRoleUpdated(ctx, entry, msg.Role, signers); err != nil { + return nil, err + } return &types.MsgGrantRoleResponse{}, nil } @@ -98,7 +106,10 @@ func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*t return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) } - roleAuths := k.roleAuthorizationsForEntry(ctx, entry) + roleAuths, err := k.roleAuthorizationsForEntry(ctx, entry) + if err != nil { + return nil, err + } if roleAuth, ok := roleAuths[msg.Role]; ok { // Policy-based path. For revoke, no new addresses are being assigned. A single signer must // satisfy the policy on its own; multi-party changes go through ProposeRoleChange. @@ -118,8 +129,13 @@ func (k msgServer) RevokeRole(ctx context.Context, msg *types.MsgRevokeRole) (*t return nil, err } - signers := k.roleChangeSigners(ctx, entry, msg.Role, nil, []string{msg.Signer}) - k.emitRoleUpdated(ctx, entry, msg.Role, signers) + signers, err := k.roleChangeSigners(ctx, entry, msg.Role, nil, []string{msg.Signer}) + if err != nil { + return nil, err + } + if err := k.emitRoleUpdated(ctx, entry, msg.Role, signers); err != nil { + return nil, err + } return &types.MsgRevokeRoleResponse{}, nil } @@ -137,7 +153,10 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types return nil, types.NewErrCodeRegistryNotFound(msg.Key.String()) } - roleAuths := k.roleAuthorizationsForEntry(ctx, entry) + roleAuths, err := k.roleAuthorizationsForEntry(ctx, entry) + if err != nil { + return nil, err + } for _, update := range msg.RoleUpdates { if roleAuth, ok := roleAuths[update.Role]; ok { // ASSIGNMENT_NEW resolves only the newly-added addresses, so authorize against the @@ -161,8 +180,13 @@ func (k msgServer) SetRoles(ctx context.Context, msg *types.MsgSetRoles) (*types for _, update := range msg.RoleUpdates { newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) - signers := k.roleChangeSigners(ctx, entry, update.Role, newAddrs, []string{msg.Signer}) - k.emitRoleUpdated(ctx, entry, update.Role, signers) + signers, err := k.roleChangeSigners(ctx, entry, update.Role, newAddrs, []string{msg.Signer}) + if err != nil { + return nil, err + } + if err := k.emitRoleUpdated(ctx, entry, update.Role, signers); err != nil { + return nil, err + } } return &types.MsgSetRolesResponse{}, nil diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index 4a2599d5fd..eb3b44171e 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -74,7 +74,10 @@ func (k Keeper) EvaluateRoleChange(ctx context.Context, key *types.RegistryKey, return types.NewErrCodeRegistryNotFound(key.String()) } - roleAuths := k.roleAuthorizationsForEntry(ctx, entry) + roleAuths, err := k.roleAuthorizationsForEntry(ctx, entry) + if err != nil { + return err + } for _, update := range roleUpdates { roleAuth, ok := roleAuths[update.Role] if !ok { @@ -131,7 +134,11 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ // Only open a new pending change when the proposer could actually contribute a valid // approval to at least one affected role. This keeps state from growing via proposals // opened by accounts that are not a required party for the change. - if !k.approverEligible(ctx, entry, newChange, proposer) { + eligible, err := k.approverEligible(ctx, entry, newChange, proposer) + if err != nil { + return "", false, err + } + if !eligible { return "", false, types.NewErrCodeUnauthorized( "proposer " + proposer + " is not eligible to approve any affected role", ) @@ -181,12 +188,20 @@ func (k Keeper) ApproveRoleChange(ctx context.Context, approver string, changeID // ignored: they can never contribute to satisfying the change, so recording them would only let a // third party grow the approval set without bound (state bloat / DoS). func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) (bool, error) { - if k.approverEligible(ctx, entry, change, approver) && !slices.Contains(change.Approvals, approver) { + eligible, err := k.approverEligible(ctx, entry, change, approver) + if err != nil { + return false, err + } + if eligible && !slices.Contains(change.Approvals, approver) { change.Approvals = append(change.Approvals, approver) k.EmitEvent(ctx, types.NewEventRoleChangeApproved(change.Id, approver)) } - if !k.pendingChangeSatisfied(ctx, entry, change) { + satisfied, err := k.pendingChangeSatisfied(ctx, entry, change) + if err != nil { + return false, err + } + if !satisfied { if err := k.SetPendingRoleChange(ctx, *change); err != nil { return false, err } @@ -206,37 +221,43 @@ func (k Keeper) recordApprovalAndMaybeApply(ctx context.Context, entry *types.Re // approverEligible reports whether approver could contribute to satisfying any of the change's // role-update gates: for a policy-governed role they must be one of the policy's referenced // addresses, and for a non-policy role they must own the NFT (the legacy fallback). -func (k Keeper) approverEligible(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) bool { - roleAuths := k.roleAuthorizationsForEntry(ctx, entry) +func (k Keeper) approverEligible(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange, approver string) (bool, error) { + roleAuths, err := k.roleAuthorizationsForEntry(ctx, entry) + if err != nil { + return false, err + } for _, update := range change.RoleUpdates { roleAuth, ok := roleAuths[update.Role] if !ok { // Non-policy role: only the NFT owner can satisfy the fallback. if k.ValidateNFTOwner(ctx, &change.Key.AssetClassId, &change.Key.NftId, approver) == nil { - return true + return true, nil } continue } newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) if k.CollectPolicyApprovers(ctx, roleAuth, entry, newAddrs)[approver] { - return true + return true, nil } } - return false + return false, nil } // pendingChangeSatisfied reports whether the accumulated approvals satisfy every affected role's // authorization policy. A role with no policy falls back to NFT ownership: at least one approver // must own the NFT. The change applies only when all role updates are satisfied (atomic gate). -func (k Keeper) pendingChangeSatisfied(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange) bool { - roleAuths := k.roleAuthorizationsForEntry(ctx, entry) +func (k Keeper) pendingChangeSatisfied(ctx context.Context, entry *types.RegistryEntry, change *types.PendingRoleChange) (bool, error) { + roleAuths, err := k.roleAuthorizationsForEntry(ctx, entry) + if err != nil { + return false, err + } for _, update := range change.RoleUpdates { roleAuth, ok := roleAuths[update.Role] if !ok { // Legacy fallback: at least one approver must own the NFT. if !k.anyApproverOwnsNFT(ctx, change) { - return false + return false, nil } continue } @@ -244,10 +265,10 @@ func (k Keeper) pendingChangeSatisfied(ctx context.Context, entry *types.Registr // Only addresses being newly added to the role are relevant for ASSIGNMENT_NEW checks. newAddrs := additions(entry.GetRoleAddrs(update.Role), update.Addresses) if k.ValidateRoleChangeAuthorization(ctx, roleAuth, entry, newAddrs, change.Approvals) != nil { - return false + return false, nil } } - return true + return true, nil } // anyApproverOwnsNFT reports whether any accumulated approver owns the change's NFT. @@ -287,8 +308,13 @@ func (k Keeper) applyPendingChange(ctx context.Context, before *types.RegistryEn } for _, update := range change.RoleUpdates { newAddrs := additions(before.GetRoleAddrs(update.Role), update.Addresses) - signers := k.roleChangeSigners(ctx, before, update.Role, newAddrs, change.Approvals) - k.emitRoleUpdated(ctx, before, update.Role, signers) + signers, err := k.roleChangeSigners(ctx, before, update.Role, newAddrs, change.Approvals) + if err != nil { + return err + } + if err := k.emitRoleUpdated(ctx, before, update.Role, signers); err != nil { + return err + } } return nil } diff --git a/x/registry/keeper/query_server.go b/x/registry/keeper/query_server.go index fb21a90ec8..d2da8d2d28 100644 --- a/x/registry/keeper/query_server.go +++ b/x/registry/keeper/query_server.go @@ -191,15 +191,9 @@ func (qs QueryServer) ValidateRoleChange(ctx context.Context, req *types.QueryVa if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - if req.Key == nil { - return nil, status.Error(codes.InvalidArgument, "key is required") - } - if err := req.Key.Validate(); err != nil { + if err := req.Validate(); err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } - if len(req.RoleUpdates) == 0 { - return nil, status.Error(codes.InvalidArgument, "at least one role update is required") - } sdkCtx := sdk.UnwrapSDKContext(ctx) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 7d03bd1ab4..43cc037492 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -354,6 +354,37 @@ func (m QueryRegistryClassesRequest) Validate() error { return nil } +// Validate validates the QueryValidateRoleChangeRequest. Beyond requiring a key and at least one +// role update, it applies the same role-update invariants as MsgSetRoles/MsgProposeRoleChange +// (known/specified roles, no duplicate roles, valid and non-duplicate addresses) and verifies that +// each approver is a valid bech32 address, so malformed requests fail fast with a precise error +// rather than surfacing as a confusing authorization or policy-evaluation failure later. +func (m QueryValidateRoleChangeRequest) Validate() error { + var errs []error + if m.Key == nil { + errs = append(errs, NewErrCodeInvalidField("key", "key is required")) + } else if err := m.Key.Validate(); err != nil { + errs = append(errs, NewErrCodeInvalidField("key", "%s", err)) + } + + errs = append(errs, validateRoleUpdates(m.RoleUpdates)...) + + seenApprovers := make(map[string]bool, len(m.Approvers)) + for _, approver := range m.Approvers { + if _, err := sdk.AccAddressFromBech32(approver); err != nil { + errs = append(errs, NewErrCodeInvalidField("approvers", "invalid address: %s", err)) + continue + } + if seenApprovers[approver] { + errs = append(errs, NewErrCodeInvalidField("approvers", "duplicate address: %s", approver)) + continue + } + seenApprovers[approver] = true + } + + return errors.Join(errs...) +} + // ValidateStringLength checks several conditions in order: // 1. length is not between the minimum and maximum length returns an ErrCodeInvalidField error. // 2. contains whitespace returns an ErrCodeInvalidField error. diff --git a/x/registry/types/msgs_test.go b/x/registry/types/msgs_test.go index 8f7dcf046d..dd241eccbf 100644 --- a/x/registry/types/msgs_test.go +++ b/x/registry/types/msgs_test.go @@ -282,3 +282,37 @@ func TestMsgRegistryBulkUpdate_ValidateBasic(t *testing.T) { }) } } + +func TestQueryValidateRoleChangeRequest_Validate(t *testing.T) { + validAddr := sdk.AccAddress("vrc_approver___________").String() + otherAddr := sdk.AccAddress("vrc_other_addr_________").String() + validKey := &RegistryKey{AssetClassId: "aclass", NftId: "nft1"} + validUpdate := RoleUpdate{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr}} + + tests := []struct { + name string + req QueryValidateRoleChangeRequest + exp string + }{ + {name: "valid", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}, Approvers: []string{validAddr}}}, + {name: "valid no approvers", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}}}, + {name: "nil key", req: QueryValidateRoleChangeRequest{Key: nil, RoleUpdates: []RoleUpdate{validUpdate}}, exp: "invalid key: key is required"}, + {name: "no role_updates", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{}}, exp: "invalid role_updates: at least one role update is required"}, + {name: "unspecified role", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_UNSPECIFIED}}}, exp: "invalid role_updates: 0: role cannot be unspecified"}, + {name: "duplicate role", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{otherAddr}}, {Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{validAddr}}}}, exp: "invalid role_updates: 1: duplicate role CONTROLLER"}, + {name: "bad address in update", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{{Role: RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{"bad"}}}}, exp: "invalid role_updates: 0: invalid address"}, + {name: "bad approver", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}, Approvers: []string{"bad"}}, exp: "invalid approvers: invalid address"}, + {name: "duplicate approver", req: QueryValidateRoleChangeRequest{Key: validKey, RoleUpdates: []RoleUpdate{validUpdate}, Approvers: []string{validAddr, validAddr}}, exp: "invalid approvers: duplicate address"}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.req.Validate() + if tc.exp == "" { + assertions.RequireErrorContents(t, err, nil) + } else { + assertions.RequireErrorContents(t, err, []string{tc.exp}) + } + }) + } +} From 0c75d5798483931123b98d01d8c87d4f33410607 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 21 Jul 2026 15:14:44 -0600 Subject: [PATCH 30/36] fix(registry): enforce role policies in RegistryBulkUpdate; clear pending on delete RegistryBulkUpdate previously only checked NFT ownership (or hard-coded authority addresses) before writing role sets. This allowed the bulk path to bypass the same multi-party authorization policies enforced by GrantRole, RevokeRole, and SetRoles. Non-authority signers updating an existing entry now have each role change validated through ValidateRoleChangeAuthorization. DeleteRegistry now removes all PendingRoleChange records for the deleted key so that stale approvals cannot be replayed if the same NFT is later re-registered. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/keeper.go | 11 +++++ x/registry/keeper/keeper_test.go | 78 ++++++++++++++++++++++++++++++++ x/registry/keeper/msg_server.go | 34 ++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/x/registry/keeper/keeper.go b/x/registry/keeper/keeper.go index 5345a4fab6..8b4cf9b663 100644 --- a/x/registry/keeper/keeper.go +++ b/x/registry/keeper/keeper.go @@ -175,6 +175,17 @@ func (k Keeper) DeleteRegistry(ctx context.Context, key *types.RegistryKey) erro return fmt.Errorf("error removing registry: %w", err) } + // Remove any pending role changes so stale approvals cannot be replayed if the NFT is re-registered. + pending, _, err := k.GetPendingRoleChanges(ctx, nil, key) + if err != nil { + return fmt.Errorf("error fetching pending role changes for cleanup: %w", err) + } + for _, pc := range pending { + if err := k.RemovePendingRoleChange(ctx, pc.Id); err != nil { + return fmt.Errorf("error removing stale pending role change %s: %w", pc.Id, err) + } + } + k.EmitEvent(ctx, types.NewEventNFTUnregistered(key)) for _, entry := range reg.Roles { k.EmitEvent(ctx, types.NewEventRoleRevoked(key, entry.Role, entry.Addresses)) diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 27cd7f0895..ea92309ddf 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -695,6 +695,84 @@ func (s *KeeperTestSuite) GenesisTest() { s.Require().Equal(genesis1, genesis2) } +// TestRegistryBulkUpdate_EnforcesRolePolicies verifies that RegistryBulkUpdate validates role +// changes through the authorization engine when updating existing entries, so it cannot bypass the +// same multi-party policies enforced by GrantRole / RevokeRole / SetRoles. +func (s *KeeperTestSuite) TestRegistryBulkUpdate_EnforcesRolePolicies() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER + + // Seed an existing entry with user1 as CONTROLLER (user1 also owns the NFT). + require := s.Require() + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.user1}}, + })) + + // user1 (current controller + NFT owner) tries to transfer CONTROLLER to user2 without user2 + // co-signing. The CONTROLLER policy requires the incoming controller to sign; this must fail. + msgServer := keeper.NewMsgServer(s.app.RegistryKeeper) + _, err := msgServer.RegistryBulkUpdate(s.ctx, &types.MsgRegistryBulkUpdate{ + Signer: s.user1, + Entries: []types.RegistryEntry{ + { + Key: key, + Roles: []types.RolesEntry{ + {Role: controllerRole, Addresses: []string{s.user2}}, + }, + }, + }, + }) + require.Error(err, "BulkUpdate should reject controller transfer without incoming controller signature") + require.Contains(err.Error(), "unauthorized") + + // The original entry must be unchanged. + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) + require.NoError(err) + require.Equal([]string{s.user1}, entry.GetRoleAddrs(controllerRole), "controller must remain user1") +} + +// TestDeleteRegistry_ClearsPendingRoleChanges verifies that DeleteRegistry removes any pending +// role change records for the deleted entry so stale approvals cannot be replayed after +// re-registration. +func (s *KeeperTestSuite) TestDeleteRegistry_ClearsPendingRoleChanges() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + + // Register the NFT with user1 as CONTROLLER. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + })) + + // Propose a controller change to user2. Because user2 (incoming controller) has not yet + // co-signed, this creates a pending change record rather than applying immediately. + changeID, applied, err := s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err) + require.False(applied, "change should be pending, not immediately applied") + require.NotEmpty(changeID) + + // Confirm the pending change exists. + pending, _, err := s.app.RegistryKeeper.GetPendingRoleChanges(s.ctx, nil, key) + require.NoError(err) + require.Len(pending, 1, "pending change should exist before delete") + + // Delete the registry entry. + require.NoError(s.app.RegistryKeeper.DeleteRegistry(s.ctx, key)) + + // The pending change must be gone: without this cleanup, re-registration of the same NFT would + // allow the stale approvals to be replayed against the new entry. + pending, _, err = s.app.RegistryKeeper.GetPendingRoleChanges(s.ctx, nil, key) + require.NoError(err) + require.Empty(pending, "pending changes must be cleared after delete") +} + func (s *KeeperTestSuite) TestRegisterNFTMsgServer() { tests := []struct { name string diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index e2b7285cb7..5ebca4dc6f 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -298,6 +298,40 @@ func (k msgServer) RegistryBulkUpdate(ctx context.Context, msg *types.MsgRegistr return nil, fmt.Errorf("could not get existing registry entry [%d]: %w", i, err) } + // When updating an existing entry, validate each role change through the authorization + // engine so that RegistryBulkUpdate cannot bypass the multi-party policies enforced by + // GrantRole, RevokeRole, and SetRoles. Authority addresses are exempt from this check (they + // also bypass NFT-ownership validation above). New registrations (orig == nil) are treated + // like RegisterNFT: NFT ownership was already verified, so initial role seeding is allowed. + if orig != nil && msg.Signer != authority1 && msg.Signer != authority2 { + roleAuths := types.RoleAuthorizationMap() + // Validate roles being set or changed to a new desired state. + for _, roleEntry := range entry.Roles { + if roleAuth, ok := roleAuths[roleEntry.Role]; ok { + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, orig, roleEntry.Addresses, []string{msg.Signer}); err != nil { + return nil, fmt.Errorf("[%d] unauthorized update for role %s: %w", i, roleEntry.Role.ShortString(), err) + } + } + } + // Validate roles being cleared (present in orig but absent from the new entry). + for _, origRole := range orig.Roles { + hasInNew := false + for _, newRole := range entry.Roles { + if newRole.Role == origRole.Role { + hasInNew = true + break + } + } + if !hasInNew { + if roleAuth, ok := roleAuths[origRole.Role]; ok { + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, orig, nil, []string{msg.Signer}); err != nil { + return nil, fmt.Errorf("[%d] unauthorized removal of role %s: %w", i, origRole.Role.ShortString(), err) + } + } + } + } + } + // Store the registry. err = k.Registry.Set(ctx, entry.Key.CollKey(), entry) if err != nil { From bcf3e08a4de4f9691862230b52fba8610f814a96 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 21 Jul 2026 15:26:55 -0600 Subject: [PATCH 31/36] fix(registry): require controller signature to unregister NFT An NFT owner could bypass multi-party role policies by unregistering and re-registering the NFT with new roles. UnregisterNFT now checks whether a CONTROLLER is set on the registry entry; if so, the signer must also be in the controller list. Ownership alone is insufficient when a controller is present. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/keeper_test.go | 49 ++++++++++++++++++++++++++++++-- x/registry/keeper/msg_server.go | 20 ++++++++++++- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index ea92309ddf..d6c4112af7 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -695,6 +695,41 @@ func (s *KeeperTestSuite) GenesisTest() { s.Require().Equal(genesis1, genesis2) } +// TestUnregisterNFT_RequiresControllerSignature verifies that an NFT owner cannot unilaterally +// unregister an NFT while a CONTROLLER is set, preventing policy bypass via unregister+re-register. +func (s *KeeperTestSuite) TestUnregisterNFT_RequiresControllerSignature() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + msgServer := keeper.NewMsgServer(s.app.RegistryKeeper) + + // Register with user1 as CONTROLLER (user1 also owns the NFT via Mint in SetupTest). + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + // user1 owns the NFT but is also the CONTROLLER, so unregistration is allowed. + _, err := msgServer.UnregisterNFT(s.ctx, &types.MsgUnregisterNFT{Signer: s.user1, Key: key}) + require.NoError(err, "controller who also owns the NFT must be able to unregister") + + // Re-register with user2 as CONTROLLER. user1 still owns the NFT. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }, "")) + + // user1 (NFT owner, but NOT the controller) tries to unregister — must be rejected. + _, err = msgServer.UnregisterNFT(s.ctx, &types.MsgUnregisterNFT{Signer: s.user1, Key: key}) + require.Error(err, "NFT owner should not be able to unregister while a different controller is set") + require.Contains(err.Error(), "unauthorized") + + // Entry must still exist. + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) + require.NoError(err) + require.NotNil(entry, "registry entry must remain after rejected unregistration") +} + // TestRegistryBulkUpdate_EnforcesRolePolicies verifies that RegistryBulkUpdate validates role // changes through the authorization engine when updating existing entries, so it cannot bypass the // same multi-party policies enforced by GrantRole / RevokeRole / SetRoles. @@ -705,11 +740,14 @@ func (s *KeeperTestSuite) TestRegistryBulkUpdate_EnforcesRolePolicies() { } controllerRole := types.RegistryRole_REGISTRY_ROLE_CONTROLLER - // Seed an existing entry with user1 as CONTROLLER (user1 also owns the NFT). + // Install the CONTROLLER policy so the authorization engine can enforce it. require := s.Require() + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ {Role: controllerRole, Addresses: []string{s.user1}}, - })) + }, "")) // user1 (current controller + NFT owner) tries to transfer CONTROLLER to user2 without user2 // co-signing. The CONTROLLER policy requires the incoming controller to sign; this must fail. @@ -744,10 +782,15 @@ func (s *KeeperTestSuite) TestDeleteRegistry_ClearsPendingRoleChanges() { } require := s.Require() + // Install the CONTROLLER policy so the authorization engine can enforce it. + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) + // Register the NFT with user1 as CONTROLLER. require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, - })) + }, "")) // Propose a controller change to user2. Because user2 (incoming controller) has not yet // co-signed, this creates a pending change record rather than applying immediately. diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 5ebca4dc6f..cdc7e9b279 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -3,6 +3,7 @@ package keeper import ( "context" "fmt" + "slices" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/gogoproto/proto" @@ -258,6 +259,20 @@ func (k msgServer) UnregisterNFT(ctx context.Context, msg *types.MsgUnregisterNF return nil, err } + // If a CONTROLLER is set on the entry, the signer must also be the CONTROLLER. Without this + // check an NFT owner could bypass multi-party role policies by unregistering and re-registering + // with new roles. + entry, err := k.GetRegistry(ctx, msg.Key) + if err != nil { + return nil, fmt.Errorf("could not get registry entry: %w", err) + } + if entry != nil { + controllers := entry.GetRoleAddrs(types.RegistryRole_REGISTRY_ROLE_CONTROLLER) + if len(controllers) > 0 && !slices.Contains(controllers, msg.Signer) { + return nil, types.NewErrCodeUnauthorized("signer is not the controller") + } + } + if err := k.DeleteRegistry(ctx, msg.Key); err != nil { return nil, err } @@ -304,7 +319,10 @@ func (k msgServer) RegistryBulkUpdate(ctx context.Context, msg *types.MsgRegistr // also bypass NFT-ownership validation above). New registrations (orig == nil) are treated // like RegisterNFT: NFT ownership was already verified, so initial role seeding is allowed. if orig != nil && msg.Signer != authority1 && msg.Signer != authority2 { - roleAuths := types.RoleAuthorizationMap() + roleAuths, err := k.roleAuthorizationsForEntry(ctx, orig) + if err != nil { + return nil, fmt.Errorf("[%d] could not get role authorizations: %w", i, err) + } // Validate roles being set or changed to a new desired state. for _, roleEntry := range entry.Roles { if roleAuth, ok := roleAuths[roleEntry.Role]; ok { From 12e75e8499244b5b4a394ef904c3af9a356f091d Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 21 Jul 2026 15:32:25 -0600 Subject: [PATCH 32/36] fix(registry): limit pending role changes to one per NFT A second ProposeRoleChange for a different set of role updates is now rejected when a pending change already exists for the same NFT key. This prevents conflicting proposals from accumulating and avoids ambiguity about which change should be applied. Re-proposing the identical role updates (same deterministic ID) is still permitted as a co-approval path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/keeper_test.go | 41 ++++++++++++++++++++++++++++++++ x/registry/keeper/pending.go | 12 ++++++++++ 2 files changed, 53 insertions(+) diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index d6c4112af7..04e0a1593c 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -816,6 +816,47 @@ func (s *KeeperTestSuite) TestDeleteRegistry_ClearsPendingRoleChanges() { require.Empty(pending, "pending changes must be cleared after delete") } +// TestProposeRoleChange_LimitOnePerNFT verifies that only one pending role change can exist for a +// given NFT at a time. A second proposal for a different set of role updates must be rejected until +// the first is cancelled or applied. +func (s *KeeperTestSuite) TestProposeRoleChange_LimitOnePerNFT() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + + // Install the CONTROLLER policy. + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) + + // Register with user1 as CONTROLLER. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + // First proposal: transfer CONTROLLER to user2. Leaves a pending change (user2 must co-sign). + _, applied, err := s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err) + require.False(applied) + + // Second proposal with different role updates must be rejected while the first is pending. + _, _, err = s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }) + require.Error(err, "second conflicting proposal should be rejected") + require.Contains(err.Error(), "pending role change already exists") + + // Re-proposing the same role updates (same ID) is allowed — it acts as a co-approval. + _, _, err = s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user2, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err, "co-approving the same pending change must be allowed") +} + func (s *KeeperTestSuite) TestRegisterNFTMsgServer() { tests := []struct { name string diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index eb3b44171e..8c315c0b61 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -125,6 +125,18 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ return "", false, err } if change == nil { + // Reject if there's already a different pending change for this key. Limiting to one + // pending change per NFT prevents conflicting proposals from accumulating; the existing + // change must be cancelled or applied first. + existing, _, err := k.GetPendingRoleChanges(ctx, nil, key) + if err != nil { + return "", false, err + } + if len(existing) > 0 { + return "", false, types.NewErrCodeInvalidField("role_updates", + "a pending role change already exists for this NFT; cancel or apply the existing change first") + } + newChange := &types.PendingRoleChange{ Id: id, Key: key, From 4504350e646109244cdba051c5314e051931998a Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 21 Jul 2026 15:56:52 -0600 Subject: [PATCH 33/36] feat(registry): add CancelRoleChange and pending change expiry Adds two new capabilities requested in review feedback: Cancel: A new MsgCancelRoleChange endpoint lets the original proposer of a pending role change remove it at any time. Non-proposers are rejected. Emits EventRoleChangeCancelled on success. Expiry: A new pending_change_expiry Duration param controls how long a pending role change remains valid. When set, the expires_at timestamp is recorded on the PendingRoleChange at proposal time. ApproveRoleChange rejects and cleans up any change that has passed its expiry. A zero value (the default) preserves the existing behavior of no expiry. Proto changes: MsgCancelRoleChange/Response and CancelRoleChange RPC in tx.proto; EventRoleChangeCancelled in events.proto; expires_at field on PendingRoleChange in registry.proto; pending_change_expiry field on Params in params.proto. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- proto/provenance/registry/v1/events.proto | 6 + proto/provenance/registry/v1/params.proto | 7 + proto/provenance/registry/v1/registry.proto | 6 + proto/provenance/registry/v1/tx.proto | 19 + x/registry/keeper/keeper_test.go | 81 +++ x/registry/keeper/msg_server.go | 19 + x/registry/keeper/pending.go | 13 + x/registry/types/events.go | 8 + x/registry/types/events.pb.go | 306 +++++++++-- x/registry/types/msgs.go | 14 + x/registry/types/params.go | 8 +- x/registry/types/params.pb.go | 94 +++- x/registry/types/registry.pb.go | 147 ++++-- x/registry/types/tx.pb.go | 533 +++++++++++++++++--- 14 files changed, 1085 insertions(+), 176 deletions(-) diff --git a/proto/provenance/registry/v1/events.proto b/proto/provenance/registry/v1/events.proto index 2b3bfbc556..c5f0aaf181 100644 --- a/proto/provenance/registry/v1/events.proto +++ b/proto/provenance/registry/v1/events.proto @@ -62,6 +62,12 @@ message EventRoleChangeApplied { string change_id = 3; } +// EventRoleChangeCancelled is emitted when a pending role change is cancelled by its proposer. +message EventRoleChangeCancelled { + string change_id = 1; + string cancelled_by = 2; +} + // EventRegistryClassCreated is emitted when a registry class is created. message EventRegistryClassCreated { string registry_class_id = 1; diff --git a/proto/provenance/registry/v1/params.proto b/proto/provenance/registry/v1/params.proto index abefdb42b6..17d1d4ec63 100644 --- a/proto/provenance/registry/v1/params.proto +++ b/proto/provenance/registry/v1/params.proto @@ -7,6 +7,7 @@ option java_multiple_files = true; import "gogoproto/gogo.proto"; import "provenance/registry/v1/authorization.proto"; +import "google/protobuf/duration.proto"; // Params defines the registry module's parameters. message Params { @@ -15,4 +16,10 @@ message Params { // the two-tier resolution: a registry class policy takes precedence, then these module defaults, // and finally roles without any policy fall back to legacy NFT-owner authorization. repeated RoleAuthorization role_authorizations = 1 [(gogoproto.nullable) = false]; + + // pending_change_expiry is how long a pending role change remains valid after it is proposed. + // Approvals received after the expiry are rejected and the change is cleaned up. + // Zero value (default) means pending changes never expire. + google.protobuf.Duration pending_change_expiry = 2 + [(gogoproto.stdduration) = true, (gogoproto.nullable) = false]; } diff --git a/proto/provenance/registry/v1/registry.proto b/proto/provenance/registry/v1/registry.proto index 0fb5ea36a6..5278d81875 100644 --- a/proto/provenance/registry/v1/registry.proto +++ b/proto/provenance/registry/v1/registry.proto @@ -7,6 +7,7 @@ option java_multiple_files = true; import "gogoproto/gogo.proto"; import "cosmos_proto/cosmos.proto"; +import "google/protobuf/timestamp.proto"; // RegistryRole defines the different types of roles that can be assigned to addresses. // These roles determine the permissions and capabilities that an address has within the registry system. @@ -122,4 +123,9 @@ message PendingRoleChange { // approvals is the accumulated set of addresses that have approved this change. repeated string approvals = 5 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // expires_at is the block time after which this pending change is no longer valid. + // Zero value means no expiry. + google.protobuf.Timestamp expires_at = 6 + [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; } \ No newline at end of file diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index 7a906205f0..596a9a169c 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -64,6 +64,10 @@ service Msg { rpc UpdateRegistryClassRoleAuthorization(MsgUpdateRegistryClassRoleAuthorization) returns (MsgUpdateRegistryClassRoleAuthorizationResponse); + // CancelRoleChange cancels an open pending role change. + // Only the original proposer may cancel. + rpc CancelRoleChange(MsgCancelRoleChange) returns (MsgCancelRoleChangeResponse); + // UpdateParams is a governance proposal endpoint for updating the registry module's params, // including the default role authorization policies. The authority must be the governance module // account address. @@ -255,6 +259,21 @@ message MsgApproveRoleChangeResponse { bool applied = 1; } +// MsgCancelRoleChange cancels an open pending role change. +// Only the original proposer may cancel. +message MsgCancelRoleChange { + option (cosmos.msg.v1.signer) = "signer"; + + // signer must be the original proposer of the pending change. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // change_id is the identifier of the pending change to cancel. + string change_id = 2; +} + +// MsgCancelRoleChangeResponse defines the response for CancelRoleChange. +message MsgCancelRoleChangeResponse {} + // MsgCreateRegistryClass creates a new registry class defining asset class-level authorization // rules for role updates within an NFT collection. message MsgCreateRegistryClass { diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 04e0a1593c..79f4700ed4 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -857,6 +857,87 @@ func (s *KeeperTestSuite) TestProposeRoleChange_LimitOnePerNFT() { require.NoError(err, "co-approving the same pending change must be allowed") } +// TestCancelRoleChange verifies that a pending role change can be cancelled by its proposer and +// that non-proposers are rejected. +func (s *KeeperTestSuite) TestCancelRoleChange() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + })) + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + changeID, applied, err := s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err) + require.False(applied) + + msgServer := keeper.NewMsgServer(s.app.RegistryKeeper) + + // Non-proposer cannot cancel. + _, err = msgServer.CancelRoleChange(s.ctx, &types.MsgCancelRoleChange{ + Signer: s.user2, + ChangeId: changeID, + }) + require.Error(err) + require.Contains(err.Error(), "unauthorized") + + // Proposer can cancel. + _, err = msgServer.CancelRoleChange(s.ctx, &types.MsgCancelRoleChange{ + Signer: s.user1, + ChangeId: changeID, + }) + require.NoError(err, "proposer must be able to cancel the pending change") + + // Change must be gone. + change, err := s.app.RegistryKeeper.GetPendingRoleChange(s.ctx, changeID) + require.NoError(err) + require.Nil(change, "pending change must not exist after cancellation") +} + +// TestPendingRoleChange_Expiry verifies that approvals on an expired pending change are rejected +// and the expired record is cleaned up. +func (s *KeeperTestSuite) TestPendingRoleChange_Expiry() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + PendingChangeExpiry: time.Hour, + })) + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + changeID, applied, err := s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err) + require.False(applied) + + // Advance block time past the expiry. + s.ctx = s.ctx.WithBlockTime(s.ctx.BlockTime().Add(2 * time.Hour)) + + // Approval after expiry must be rejected and the record removed. + _, err = s.app.RegistryKeeper.ApproveRoleChange(s.ctx, s.user2, changeID) + require.Error(err, "approving an expired change must fail") + require.Contains(err.Error(), "pending role change not found") + + change, err := s.app.RegistryKeeper.GetPendingRoleChange(s.ctx, changeID) + require.NoError(err) + require.Nil(change, "expired change must be removed on failed approval") +} + func (s *KeeperTestSuite) TestRegisterNFTMsgServer() { tests := []struct { name string diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index cdc7e9b279..3abfc56232 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -213,6 +213,25 @@ func (k msgServer) ApproveRoleChange(ctx context.Context, msg *types.MsgApproveR return &types.MsgApproveRoleChangeResponse{Applied: applied}, nil } +// CancelRoleChange cancels an open pending role change. Only the original proposer may cancel. +func (k msgServer) CancelRoleChange(ctx context.Context, msg *types.MsgCancelRoleChange) (*types.MsgCancelRoleChangeResponse, error) { + change, err := k.GetPendingRoleChange(ctx, msg.ChangeId) + if err != nil { + return nil, err + } + if change == nil { + return nil, types.NewErrCodePendingChangeNotFound(msg.ChangeId) + } + if change.Proposer != msg.Signer { + return nil, types.NewErrCodeUnauthorized("only the original proposer may cancel a pending role change") + } + if err := k.RemovePendingRoleChange(ctx, msg.ChangeId); err != nil { + return nil, err + } + k.EmitEvent(ctx, types.NewEventRoleChangeCancelled(msg.ChangeId, msg.Signer)) + return &types.MsgCancelRoleChangeResponse{}, nil +} + // CreateRegistryClass creates a new registry class defining asset class-level authorization rules. func (k msgServer) CreateRegistryClass(ctx context.Context, msg *types.MsgCreateRegistryClass) (*types.MsgCreateRegistryClassResponse, error) { class := types.RegistryClass{ diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index 8c315c0b61..98473d5bc1 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -7,6 +7,7 @@ import ( "cosmossdk.io/collections" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" "github.com/provenance-io/provenance/x/registry/types" @@ -143,6 +144,10 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ RoleUpdates: roleUpdates, Proposer: proposer, } + // Set expiry if the module params configure one. + if expiry := k.GetParams(ctx).PendingChangeExpiry; expiry > 0 { + newChange.ExpiresAt = sdk.UnwrapSDKContext(ctx).BlockTime().Add(expiry) + } // Only open a new pending change when the proposer could actually contribute a valid // approval to at least one affected role. This keeps state from growing via proposals // opened by accounts that are not a required party for the change. @@ -177,6 +182,14 @@ func (k Keeper) ApproveRoleChange(ctx context.Context, approver string, changeID return false, types.NewErrCodePendingChangeNotFound(changeID) } + // Reject and clean up expired changes. + if !change.ExpiresAt.IsZero() && !sdk.UnwrapSDKContext(ctx).BlockTime().Before(change.ExpiresAt) { + if rerr := k.RemovePendingRoleChange(ctx, changeID); rerr != nil { + return false, rerr + } + return false, types.NewErrCodePendingChangeNotFound(changeID + " (expired)") + } + entry, err := k.GetRegistry(ctx, change.Key) if err != nil { return false, err diff --git a/x/registry/types/events.go b/x/registry/types/events.go index d28cf31fdf..9fb96bd081 100644 --- a/x/registry/types/events.go +++ b/x/registry/types/events.go @@ -76,6 +76,14 @@ func NewEventRoleChangeApplied(change *PendingRoleChange) *EventRoleChangeApplie } } +// NewEventRoleChangeCancelled returns a new EventRoleChangeCancelled. +func NewEventRoleChangeCancelled(changeID, cancelledBy string) *EventRoleChangeCancelled { + return &EventRoleChangeCancelled{ + ChangeId: changeID, + CancelledBy: cancelledBy, + } +} + // NewEventRegistryClassCreated returns a new EventRegistryClassCreated. func NewEventRegistryClassCreated(class *RegistryClass) *EventRegistryClassCreated { return &EventRegistryClassCreated{ diff --git a/x/registry/types/events.pb.go b/x/registry/types/events.pb.go index 55dfeb2397..ec806f3547 100644 --- a/x/registry/types/events.pb.go +++ b/x/registry/types/events.pb.go @@ -503,6 +503,59 @@ func (m *EventRoleChangeApplied) GetChangeId() string { return "" } +// EventRoleChangeCancelled is emitted when a pending role change is cancelled by its proposer. +type EventRoleChangeCancelled struct { + ChangeId string `protobuf:"bytes,1,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` + CancelledBy string `protobuf:"bytes,2,opt,name=cancelled_by,json=cancelledBy,proto3" json:"cancelled_by,omitempty"` +} + +func (m *EventRoleChangeCancelled) Reset() { *m = EventRoleChangeCancelled{} } +func (m *EventRoleChangeCancelled) String() string { return proto.CompactTextString(m) } +func (*EventRoleChangeCancelled) ProtoMessage() {} +func (*EventRoleChangeCancelled) Descriptor() ([]byte, []int) { + return fileDescriptor_61a0995529587ff0, []int{8} +} +func (m *EventRoleChangeCancelled) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *EventRoleChangeCancelled) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_EventRoleChangeCancelled.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *EventRoleChangeCancelled) XXX_Merge(src proto.Message) { + xxx_messageInfo_EventRoleChangeCancelled.Merge(m, src) +} +func (m *EventRoleChangeCancelled) XXX_Size() int { + return m.Size() +} +func (m *EventRoleChangeCancelled) XXX_DiscardUnknown() { + xxx_messageInfo_EventRoleChangeCancelled.DiscardUnknown(m) +} + +var xxx_messageInfo_EventRoleChangeCancelled proto.InternalMessageInfo + +func (m *EventRoleChangeCancelled) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +func (m *EventRoleChangeCancelled) GetCancelledBy() string { + if m != nil { + return m.CancelledBy + } + return "" +} + // EventRegistryClassCreated is emitted when a registry class is created. type EventRegistryClassCreated struct { RegistryClassId string `protobuf:"bytes,1,opt,name=registry_class_id,json=registryClassId,proto3" json:"registry_class_id,omitempty"` @@ -514,7 +567,7 @@ func (m *EventRegistryClassCreated) Reset() { *m = EventRegistryClassCre func (m *EventRegistryClassCreated) String() string { return proto.CompactTextString(m) } func (*EventRegistryClassCreated) ProtoMessage() {} func (*EventRegistryClassCreated) Descriptor() ([]byte, []int) { - return fileDescriptor_61a0995529587ff0, []int{8} + return fileDescriptor_61a0995529587ff0, []int{9} } func (m *EventRegistryClassCreated) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -574,7 +627,7 @@ func (m *EventRegistryClassUpdated) Reset() { *m = EventRegistryClassUpd func (m *EventRegistryClassUpdated) String() string { return proto.CompactTextString(m) } func (*EventRegistryClassUpdated) ProtoMessage() {} func (*EventRegistryClassUpdated) Descriptor() ([]byte, []int) { - return fileDescriptor_61a0995529587ff0, []int{9} + return fileDescriptor_61a0995529587ff0, []int{10} } func (m *EventRegistryClassUpdated) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -625,7 +678,7 @@ func (m *EventParamsUpdated) Reset() { *m = EventParamsUpdated{} } func (m *EventParamsUpdated) String() string { return proto.CompactTextString(m) } func (*EventParamsUpdated) ProtoMessage() {} func (*EventParamsUpdated) Descriptor() ([]byte, []int) { - return fileDescriptor_61a0995529587ff0, []int{10} + return fileDescriptor_61a0995529587ff0, []int{11} } func (m *EventParamsUpdated) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -669,7 +722,7 @@ func (m *RoleSigner) Reset() { *m = RoleSigner{} } func (m *RoleSigner) String() string { return proto.CompactTextString(m) } func (*RoleSigner) ProtoMessage() {} func (*RoleSigner) Descriptor() ([]byte, []int) { - return fileDescriptor_61a0995529587ff0, []int{11} + return fileDescriptor_61a0995529587ff0, []int{12} } func (m *RoleSigner) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -743,7 +796,7 @@ func (m *EventRoleUpdated) Reset() { *m = EventRoleUpdated{} } func (m *EventRoleUpdated) String() string { return proto.CompactTextString(m) } func (*EventRoleUpdated) ProtoMessage() {} func (*EventRoleUpdated) Descriptor() ([]byte, []int) { - return fileDescriptor_61a0995529587ff0, []int{12} + return fileDescriptor_61a0995529587ff0, []int{13} } func (m *EventRoleUpdated) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -830,6 +883,7 @@ func init() { proto.RegisterType((*EventRoleChangeProposed)(nil), "provenance.registry.v1.EventRoleChangeProposed") proto.RegisterType((*EventRoleChangeApproved)(nil), "provenance.registry.v1.EventRoleChangeApproved") proto.RegisterType((*EventRoleChangeApplied)(nil), "provenance.registry.v1.EventRoleChangeApplied") + proto.RegisterType((*EventRoleChangeCancelled)(nil), "provenance.registry.v1.EventRoleChangeCancelled") proto.RegisterType((*EventRegistryClassCreated)(nil), "provenance.registry.v1.EventRegistryClassCreated") proto.RegisterType((*EventRegistryClassUpdated)(nil), "provenance.registry.v1.EventRegistryClassUpdated") proto.RegisterType((*EventParamsUpdated)(nil), "provenance.registry.v1.EventParamsUpdated") @@ -842,43 +896,45 @@ func init() { } var fileDescriptor_61a0995529587ff0 = []byte{ - // 568 bytes of a gzipped FileDescriptorProto + // 597 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x55, 0xdd, 0x6e, 0xd3, 0x30, - 0x14, 0xae, 0xdb, 0xae, 0x5b, 0x0f, 0x08, 0x58, 0x54, 0x46, 0x36, 0x50, 0xa8, 0x02, 0x17, 0x15, - 0xd2, 0x12, 0x0d, 0x78, 0x81, 0xb5, 0x02, 0xd4, 0x1b, 0x54, 0x32, 0x26, 0x24, 0x2e, 0xa8, 0xbc, - 0xc6, 0xcb, 0xac, 0xb6, 0x76, 0x64, 0xbb, 0x11, 0xbb, 0xe4, 0x01, 0x10, 0x3c, 0x02, 0x8f, 0xb3, - 0xcb, 0x5d, 0x72, 0x85, 0x50, 0xfb, 0x22, 0x28, 0x4e, 0xd3, 0xa4, 0x3f, 0x93, 0x40, 0x1d, 0xbb, - 0x8b, 0xcf, 0xf9, 0xce, 0x77, 0x3e, 0x7f, 0xce, 0xb1, 0xe1, 0x49, 0x28, 0x78, 0x44, 0x18, 0x66, - 0x3d, 0xe2, 0x0a, 0x12, 0x50, 0xa9, 0xc4, 0xb9, 0x1b, 0x1d, 0xb8, 0x24, 0x22, 0x4c, 0x49, 0x27, - 0x14, 0x5c, 0x71, 0x63, 0x27, 0x03, 0x39, 0x29, 0xc8, 0x89, 0x0e, 0xf6, 0x6a, 0x01, 0x0f, 0xb8, - 0x86, 0xb8, 0xf1, 0x57, 0x82, 0xb6, 0xdf, 0x81, 0xf1, 0x2a, 0xae, 0x7e, 0xfb, 0xfa, 0xbd, 0xa7, - 0xc1, 0x44, 0x10, 0xdf, 0xb8, 0x0f, 0x15, 0x76, 0xaa, 0xba, 0xd4, 0x37, 0x51, 0x1d, 0x35, 0xaa, - 0xde, 0x06, 0x3b, 0x55, 0x6d, 0xdf, 0x78, 0x0a, 0x77, 0xb0, 0x94, 0x44, 0x75, 0x7b, 0x03, 0x2c, - 0x65, 0x9c, 0x2e, 0xea, 0xf4, 0x6d, 0x1d, 0x6d, 0xc5, 0xc1, 0xb6, 0x6f, 0x7f, 0x41, 0x70, 0x4f, - 0x73, 0x7a, 0x7c, 0x40, 0xde, 0x08, 0xcc, 0xd4, 0x9a, 0x8c, 0x86, 0x01, 0x65, 0xc1, 0x07, 0xc4, - 0x2c, 0xe9, 0x9c, 0xfe, 0x36, 0x1e, 0x41, 0x15, 0xfb, 0xbe, 0x20, 0x52, 0x12, 0x69, 0x96, 0xeb, - 0xa5, 0x46, 0xd5, 0xcb, 0x02, 0xf3, 0x1a, 0x3c, 0x12, 0xf1, 0xfe, 0xcd, 0x6b, 0x38, 0x82, 0x5a, - 0x6a, 0xed, 0x31, 0x13, 0xd7, 0x64, 0xee, 0x07, 0x30, 0x93, 0x7d, 0x4d, 0x4f, 0xb6, 0x39, 0x1a, - 0xf4, 0x8f, 0x43, 0x1f, 0xaf, 0xeb, 0xb1, 0xfd, 0x0d, 0xc1, 0x83, 0x99, 0x63, 0xad, 0x33, 0xcc, - 0x02, 0xd2, 0x11, 0x3c, 0xe4, 0x72, 0x5d, 0xe3, 0x1e, 0x42, 0xb5, 0xa7, 0xe9, 0x62, 0x40, 0xe2, - 0xde, 0x56, 0x12, 0x68, 0xfb, 0xc6, 0x1e, 0x6c, 0x85, 0x49, 0x17, 0x61, 0x96, 0x93, 0x5c, 0xba, - 0xb6, 0xbd, 0x25, 0x41, 0x87, 0xa1, 0xfe, 0xb7, 0x17, 0x38, 0xd1, 0x32, 0x27, 0x4e, 0x80, 0x62, - 0x2a, 0x68, 0xb6, 0xb6, 0x05, 0xec, 0x2c, 0x73, 0x0e, 0xe8, 0xff, 0xdc, 0xa3, 0xfd, 0x15, 0xc1, - 0xee, 0xdc, 0x99, 0xe9, 0xaa, 0x96, 0x20, 0xfa, 0xd0, 0x9e, 0xc1, 0x76, 0x3a, 0xa5, 0x59, 0x8f, - 0x44, 0xc2, 0x5d, 0x91, 0x2f, 0xf8, 0x6b, 0x31, 0x16, 0xc0, 0x10, 0x53, 0xa6, 0x30, 0x65, 0x44, - 0x4c, 0xd5, 0xe4, 0x22, 0x76, 0xb0, 0x4a, 0x4e, 0xfa, 0x0f, 0xfd, 0x8b, 0x9c, 0xf9, 0x46, 0xc5, - 0xa5, 0x46, 0xb5, 0xe9, 0xdd, 0xd2, 0xc1, 0x02, 0x0f, 0xd3, 0x0e, 0xf6, 0x27, 0x80, 0xd8, 0xfd, - 0x23, 0x1a, 0x30, 0x22, 0x66, 0x63, 0x85, 0x72, 0x63, 0x65, 0x01, 0x60, 0x29, 0x69, 0xc0, 0x86, - 0x84, 0xa9, 0x94, 0x37, 0x8b, 0xcc, 0x8f, 0x5d, 0x69, 0x71, 0xec, 0x7e, 0x14, 0x73, 0xa3, 0x7f, - 0x1d, 0xa3, 0xb1, 0xda, 0x93, 0xd2, 0x6a, 0x4f, 0xd2, 0xfd, 0x94, 0xaf, 0xba, 0x26, 0x36, 0x16, - 0xf4, 0x1a, 0xfb, 0x60, 0x84, 0x82, 0x44, 0x94, 0x8f, 0x64, 0x37, 0x83, 0x55, 0x34, 0x6c, 0x3b, - 0xcd, 0x1c, 0xce, 0xe0, 0x4d, 0xd8, 0x94, 0xda, 0x3a, 0x69, 0x6e, 0xd6, 0x4b, 0x8d, 0x5b, 0xcf, - 0x6d, 0x67, 0xf5, 0x85, 0xef, 0x64, 0x2e, 0x37, 0xcb, 0x17, 0xbf, 0x1e, 0x17, 0xbc, 0xb4, 0xb0, - 0xd9, 0xbf, 0x18, 0x5b, 0xe8, 0x72, 0x6c, 0xa1, 0xdf, 0x63, 0x0b, 0x7d, 0x9f, 0x58, 0x85, 0xcb, - 0x89, 0x55, 0xf8, 0x39, 0xb1, 0x0a, 0xb0, 0x4b, 0xf9, 0x15, 0x74, 0x1d, 0xf4, 0xf1, 0x65, 0x40, - 0xd5, 0xd9, 0xe8, 0xc4, 0xe9, 0xf1, 0xa1, 0x9b, 0x81, 0xf6, 0x29, 0xcf, 0xad, 0xdc, 0xcf, 0xd9, - 0xcb, 0xa4, 0xce, 0x43, 0x22, 0x4f, 0x2a, 0xfa, 0xa1, 0x79, 0xf1, 0x27, 0x00, 0x00, 0xff, 0xff, - 0x23, 0xb6, 0xf0, 0xd0, 0xbd, 0x06, 0x00, 0x00, + 0x14, 0x6e, 0xd6, 0xee, 0xa7, 0x67, 0x13, 0xb0, 0xa8, 0x8c, 0x6c, 0xa0, 0x50, 0x02, 0x17, 0x15, + 0xd2, 0x12, 0x0d, 0x78, 0x81, 0xb5, 0x02, 0xd4, 0x1b, 0x54, 0x32, 0x26, 0xa4, 0x5d, 0x50, 0xb9, + 0x8d, 0x97, 0x45, 0x4d, 0xed, 0xc8, 0x76, 0x23, 0x7a, 0xc9, 0x03, 0x20, 0x78, 0x04, 0x1e, 0x67, + 0x97, 0xbb, 0xe4, 0x0a, 0xa1, 0xf6, 0x45, 0x50, 0x9c, 0x3a, 0xe9, 0xdf, 0x04, 0xa8, 0x83, 0xbb, + 0xf8, 0x9c, 0xcf, 0xdf, 0xf9, 0xce, 0x17, 0x1f, 0x1b, 0x1e, 0x47, 0x8c, 0xc6, 0x98, 0x20, 0xd2, + 0xc5, 0x0e, 0xc3, 0x7e, 0xc0, 0x05, 0x1b, 0x3a, 0xf1, 0x91, 0x83, 0x63, 0x4c, 0x04, 0xb7, 0x23, + 0x46, 0x05, 0xd5, 0xf7, 0x72, 0x90, 0xad, 0x40, 0x76, 0x7c, 0x74, 0x50, 0xf1, 0xa9, 0x4f, 0x25, + 0xc4, 0x49, 0xbe, 0x52, 0xb4, 0xf5, 0x16, 0xf4, 0x97, 0xc9, 0xee, 0x37, 0xaf, 0xde, 0xb9, 0x12, + 0x8c, 0x19, 0xf6, 0xf4, 0xbb, 0xb0, 0x41, 0xce, 0x45, 0x3b, 0xf0, 0x0c, 0xad, 0xaa, 0xd5, 0xca, + 0xee, 0x3a, 0x39, 0x17, 0x4d, 0x4f, 0x7f, 0x02, 0xb7, 0x10, 0xe7, 0x58, 0xb4, 0xbb, 0x21, 0xe2, + 0x3c, 0x49, 0xaf, 0xc9, 0xf4, 0x8e, 0x8c, 0x36, 0x92, 0x60, 0xd3, 0xb3, 0x3e, 0x69, 0x70, 0x47, + 0x72, 0xba, 0x34, 0xc4, 0xaf, 0x19, 0x22, 0x62, 0x45, 0x46, 0x5d, 0x87, 0x12, 0xa3, 0x21, 0x36, + 0x8a, 0x32, 0x27, 0xbf, 0xf5, 0x07, 0x50, 0x46, 0x9e, 0xc7, 0x30, 0xe7, 0x98, 0x1b, 0xa5, 0x6a, + 0xb1, 0x56, 0x76, 0xf3, 0xc0, 0xac, 0x06, 0x17, 0xc7, 0xb4, 0xf7, 0xff, 0x35, 0x9c, 0x40, 0x45, + 0x59, 0x7b, 0x4a, 0xd8, 0x0d, 0x99, 0xfb, 0x1e, 0x8c, 0xb4, 0xaf, 0xc9, 0x9f, 0xad, 0x0f, 0xc2, + 0xde, 0x69, 0xe4, 0xa1, 0x55, 0x3d, 0xb6, 0xbe, 0x68, 0x70, 0x2f, 0x73, 0xac, 0x71, 0x81, 0x88, + 0x8f, 0x5b, 0x8c, 0x46, 0x94, 0xaf, 0x6a, 0xdc, 0x7d, 0x28, 0x77, 0x25, 0x5d, 0x02, 0x48, 0xdd, + 0xdb, 0x4a, 0x03, 0x4d, 0x4f, 0x3f, 0x80, 0xad, 0x28, 0xad, 0xc2, 0x8c, 0x52, 0x9a, 0x53, 0x6b, + 0xcb, 0x5d, 0x10, 0x74, 0x1c, 0xc9, 0xb3, 0x3d, 0xc7, 0xa9, 0x2d, 0x72, 0xa2, 0x14, 0xc8, 0x26, + 0x82, 0xb2, 0xb5, 0xc5, 0x60, 0x6f, 0x91, 0x33, 0x0c, 0xfe, 0x65, 0x8f, 0xd6, 0x99, 0xfa, 0x65, + 0x59, 0xcd, 0x46, 0x32, 0x9c, 0x61, 0xf8, 0xbb, 0x46, 0x1e, 0xc1, 0x4e, 0x57, 0x21, 0xdb, 0x9d, + 0xe1, 0xa4, 0xf2, 0x76, 0x16, 0xab, 0x0f, 0xad, 0xcf, 0x1a, 0xec, 0xcf, 0x9c, 0x07, 0xa9, 0xa8, + 0xc1, 0xb0, 0x3c, 0x10, 0x4f, 0x61, 0x57, 0xdd, 0x00, 0xb9, 0xfe, 0xb4, 0xca, 0x6d, 0x36, 0xbd, + 0xe1, 0x8f, 0x1b, 0x35, 0x01, 0xfa, 0x28, 0x20, 0x02, 0x05, 0x04, 0xb3, 0x49, 0xa7, 0x53, 0x11, + 0xcb, 0x5f, 0x26, 0x47, 0x9d, 0xcf, 0xbf, 0x91, 0x33, 0x5b, 0x68, 0x6d, 0xa1, 0x50, 0x65, 0x72, + 0x6f, 0xb5, 0x10, 0x43, 0x7d, 0x55, 0xc1, 0xfa, 0x00, 0x90, 0xb8, 0x7c, 0x12, 0xf8, 0x04, 0xb3, + 0x6c, 0x64, 0xb5, 0xa9, 0x91, 0x35, 0x01, 0x10, 0xe7, 0x81, 0x4f, 0xfa, 0x98, 0x08, 0xc5, 0x9b, + 0x47, 0x66, 0x47, 0xba, 0x38, 0x3f, 0xd2, 0xdf, 0xd6, 0xa6, 0xae, 0x95, 0x9b, 0x18, 0xbb, 0xe5, + 0x9e, 0x14, 0x97, 0x7b, 0xa2, 0xfa, 0x29, 0x5d, 0x77, 0x05, 0xad, 0xcf, 0xe9, 0xd5, 0x0f, 0x41, + 0x8f, 0x18, 0x8e, 0x03, 0x3a, 0xe0, 0xed, 0x1c, 0xb6, 0x21, 0x61, 0xbb, 0x2a, 0x73, 0x9c, 0xc1, + 0xeb, 0xb0, 0xc9, 0xa5, 0x75, 0xdc, 0xd8, 0xac, 0x16, 0x6b, 0xdb, 0xcf, 0x2c, 0x7b, 0xf9, 0x63, + 0x62, 0xe7, 0x2e, 0xd7, 0x4b, 0x97, 0x3f, 0x1e, 0x16, 0x5c, 0xb5, 0xb1, 0xde, 0xbb, 0x1c, 0x99, + 0xda, 0xd5, 0xc8, 0xd4, 0x7e, 0x8e, 0x4c, 0xed, 0xeb, 0xd8, 0x2c, 0x5c, 0x8d, 0xcd, 0xc2, 0xf7, + 0xb1, 0x59, 0x80, 0xfd, 0x80, 0x5e, 0x43, 0xd7, 0xd2, 0xce, 0x5e, 0xf8, 0x81, 0xb8, 0x18, 0x74, + 0xec, 0x2e, 0xed, 0x3b, 0x39, 0xe8, 0x30, 0xa0, 0x53, 0x2b, 0xe7, 0x63, 0xfe, 0xea, 0x89, 0x61, + 0x84, 0x79, 0x67, 0x43, 0x3e, 0x62, 0xcf, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0x71, 0x63, 0xc5, + 0xb1, 0x19, 0x07, 0x00, 0x00, } func (m *EventNFTRegistered) Marshal() (dAtA []byte, err error) { @@ -1230,6 +1286,43 @@ func (m *EventRoleChangeApplied) MarshalToSizedBuffer(dAtA []byte) (int, error) return len(dAtA) - i, nil } +func (m *EventRoleChangeCancelled) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *EventRoleChangeCancelled) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *EventRoleChangeCancelled) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.CancelledBy) > 0 { + i -= len(m.CancelledBy) + copy(dAtA[i:], m.CancelledBy) + i = encodeVarintEvents(dAtA, i, uint64(len(m.CancelledBy))) + i-- + dAtA[i] = 0x12 + } + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintEvents(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *EventRegistryClassCreated) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1642,6 +1735,23 @@ func (m *EventRoleChangeApplied) Size() (n int) { return n } +func (m *EventRoleChangeCancelled) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + l = len(m.CancelledBy) + if l > 0 { + n += 1 + l + sovEvents(uint64(l)) + } + return n +} + func (m *EventRegistryClassCreated) Size() (n int) { if m == nil { return 0 @@ -2897,6 +3007,120 @@ func (m *EventRoleChangeApplied) Unmarshal(dAtA []byte) error { } return nil } +func (m *EventRoleChangeCancelled) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: EventRoleChangeCancelled: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: EventRoleChangeCancelled: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CancelledBy", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowEvents + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthEvents + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthEvents + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CancelledBy = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipEvents(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthEvents + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *EventRegistryClassCreated) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index 43cc037492..a49b380e2e 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -177,6 +177,20 @@ func (m MsgApproveRoleChange) ValidateBasic() error { return errors.Join(errs...) } +// ValidateBasic validates the MsgCancelRoleChange message +func (m MsgCancelRoleChange) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + } + + if len(m.ChangeId) == 0 { + errs = append(errs, NewErrCodeInvalidField("change_id", "change_id is required")) + } + + return errors.Join(errs...) +} + // ValidateBasic validates the MsgCreateRegistryClass message func (m MsgCreateRegistryClass) ValidateBasic() error { var errs []error diff --git a/x/registry/types/params.go b/x/registry/types/params.go index 4866a88c90..94fa98adaf 100644 --- a/x/registry/types/params.go +++ b/x/registry/types/params.go @@ -14,10 +14,12 @@ func DefaultParams() Params { // Validate validates the Params. func (p Params) Validate() error { - if errs := validateRoleAuthorizations(p.RoleAuthorizations); len(errs) > 0 { - return errors.Join(errs...) + var errs []error + errs = append(errs, validateRoleAuthorizations(p.RoleAuthorizations)...) + if p.PendingChangeExpiry < 0 { + errs = append(errs, NewErrCodeInvalidField("pending_change_expiry", "must be non-negative")) } - return nil + return errors.Join(errs...) } // RoleAuthorizationMap returns a map of RegistryRole -> RoleAuthorization for the params' default diff --git a/x/registry/types/params.pb.go b/x/registry/types/params.pb.go index 2caa423309..8546bc8153 100644 --- a/x/registry/types/params.pb.go +++ b/x/registry/types/params.pb.go @@ -7,15 +7,19 @@ import ( fmt "fmt" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/durationpb" io "io" math "math" math_bits "math/bits" + time "time" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. @@ -30,6 +34,10 @@ type Params struct { // the two-tier resolution: a registry class policy takes precedence, then these module defaults, // and finally roles without any policy fall back to legacy NFT-owner authorization. RoleAuthorizations []RoleAuthorization `protobuf:"bytes,1,rep,name=role_authorizations,json=roleAuthorizations,proto3" json:"role_authorizations"` + // pending_change_expiry is how long a pending role change remains valid after it is proposed. + // Approvals received after the expiry are rejected and the change is cleaned up. + // Zero value (default) means pending changes never expire. + PendingChangeExpiry time.Duration `protobuf:"bytes,2,opt,name=pending_change_expiry,json=pendingChangeExpiry,proto3,stdduration" json:"pending_change_expiry"` } func (m *Params) Reset() { *m = Params{} } @@ -72,6 +80,13 @@ func (m *Params) GetRoleAuthorizations() []RoleAuthorization { return nil } +func (m *Params) GetPendingChangeExpiry() time.Duration { + if m != nil { + return m.PendingChangeExpiry + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "provenance.registry.v1.Params") } @@ -81,22 +96,26 @@ func init() { } var fileDescriptor_5e5f5edf5698b7c6 = []byte{ - // 225 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x2e, 0x28, 0xca, 0x2f, - 0x4b, 0xcd, 0x4b, 0xcc, 0x4b, 0x4e, 0xd5, 0x2f, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, 0x29, 0xaa, 0xd4, - 0x2f, 0x33, 0xd4, 0x2f, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, - 0x12, 0x43, 0x28, 0xd2, 0x83, 0x29, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, - 0x2b, 0xd1, 0x07, 0xb1, 0x20, 0xaa, 0xa5, 0xb4, 0x70, 0x18, 0x99, 0x58, 0x5a, 0x92, 0x91, 0x5f, - 0x94, 0x59, 0x95, 0x58, 0x92, 0x99, 0x9f, 0x07, 0x51, 0xab, 0x94, 0xc5, 0xc5, 0x16, 0x00, 0xb6, - 0x49, 0x28, 0x81, 0x4b, 0xb8, 0x28, 0x3f, 0x27, 0x35, 0x1e, 0x45, 0x55, 0xb1, 0x04, 0xa3, 0x02, - 0xb3, 0x06, 0xb7, 0x91, 0xa6, 0x1e, 0x76, 0x17, 0xe8, 0x05, 0xe5, 0xe7, 0xa4, 0x3a, 0x22, 0xeb, - 0x70, 0x62, 0x39, 0x71, 0x4f, 0x9e, 0x21, 0x48, 0xa8, 0x08, 0x5d, 0xa2, 0xd8, 0x29, 0xfb, 0xc4, - 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, - 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xb8, 0x24, 0x33, 0xf3, 0x71, 0x58, 0x10, 0xc0, - 0x18, 0x65, 0x92, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0xab, 0x8f, 0x50, 0xa4, - 0x9b, 0x99, 0x8f, 0xc4, 0xd3, 0xaf, 0x40, 0xf8, 0xb4, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, - 0xec, 0x3f, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x69, 0x29, 0xb2, 0xb7, 0x60, 0x01, 0x00, - 0x00, + // 304 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x90, 0xc1, 0x4e, 0x2a, 0x31, + 0x14, 0x40, 0xa7, 0xef, 0x19, 0x62, 0x86, 0xdd, 0xa0, 0x06, 0x58, 0x14, 0xa2, 0x1b, 0x34, 0xb1, + 0x0d, 0xe8, 0x0f, 0x88, 0xba, 0x27, 0x6c, 0x4c, 0xdc, 0x60, 0x81, 0x5a, 0x1a, 0xa1, 0xb7, 0xe9, + 0x74, 0x08, 0xe3, 0x57, 0xb8, 0xf4, 0x7f, 0xdc, 0xb0, 0x64, 0xe9, 0x4a, 0xcd, 0xcc, 0x8f, 0x18, + 0xca, 0x90, 0x19, 0x8d, 0xec, 0x7a, 0x73, 0xcf, 0x3d, 0x39, 0xa9, 0x7f, 0xa2, 0x0d, 0xcc, 0xb9, + 0x62, 0x6a, 0xc4, 0xa9, 0xe1, 0x42, 0x86, 0xd6, 0xc4, 0x74, 0xde, 0xa6, 0x9a, 0x19, 0x36, 0x0b, + 0x89, 0x36, 0x60, 0x21, 0x38, 0xca, 0x21, 0xb2, 0x85, 0xc8, 0xbc, 0x5d, 0x3f, 0x10, 0x20, 0xc0, + 0x21, 0x74, 0xfd, 0xda, 0xd0, 0xf5, 0xb3, 0x1d, 0x4a, 0x16, 0xd9, 0x09, 0x18, 0xf9, 0xcc, 0xac, + 0x04, 0x95, 0xb1, 0x58, 0x00, 0x88, 0x29, 0xa7, 0x6e, 0x1a, 0x46, 0x8f, 0x74, 0x1c, 0x99, 0xc2, + 0xfe, 0xf8, 0x0d, 0xf9, 0xa5, 0x9e, 0x4b, 0x09, 0x1e, 0xfc, 0x8a, 0x81, 0x29, 0x1f, 0xfc, 0xd0, + 0x84, 0x55, 0xd4, 0xfc, 0xdf, 0x2a, 0x77, 0x4e, 0xc9, 0xdf, 0x89, 0xa4, 0x0f, 0x53, 0x7e, 0x55, + 0xbc, 0xe8, 0xee, 0x2d, 0x3f, 0x1a, 0x5e, 0x3f, 0x30, 0xbf, 0x17, 0x61, 0x70, 0xe7, 0x1f, 0x6a, + 0xae, 0xc6, 0x52, 0x89, 0xc1, 0x68, 0xc2, 0x94, 0xe0, 0x03, 0xbe, 0xd0, 0xd2, 0xc4, 0xd5, 0x7f, + 0x4d, 0xd4, 0x2a, 0x77, 0x6a, 0x64, 0x13, 0x4b, 0xb6, 0xb1, 0xe4, 0x26, 0x8b, 0xed, 0xee, 0xaf, + 0x9d, 0xaf, 0x9f, 0x0d, 0xd4, 0xaf, 0x64, 0x86, 0x6b, 0x27, 0xb8, 0x75, 0xf7, 0xdd, 0xa7, 0x65, + 0x82, 0xd1, 0x2a, 0xc1, 0xe8, 0x2b, 0xc1, 0xe8, 0x25, 0xc5, 0xde, 0x2a, 0xc5, 0xde, 0x7b, 0x8a, + 0x3d, 0xbf, 0x26, 0x61, 0x47, 0x79, 0x0f, 0xdd, 0x5f, 0x0a, 0x69, 0x27, 0xd1, 0x90, 0x8c, 0x60, + 0x46, 0x73, 0xe8, 0x5c, 0x42, 0x61, 0xa2, 0x8b, 0xfc, 0x8f, 0x6d, 0xac, 0x79, 0x38, 0x2c, 0xb9, + 0xbc, 0x8b, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7c, 0x36, 0xdf, 0xd4, 0xda, 0x01, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -119,6 +138,14 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.PendingChangeExpiry, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.PendingChangeExpiry):]) + if err1 != nil { + return 0, err1 + } + i -= n1 + i = encodeVarintParams(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x12 if len(m.RoleAuthorizations) > 0 { for iNdEx := len(m.RoleAuthorizations) - 1; iNdEx >= 0; iNdEx-- { { @@ -159,6 +186,8 @@ func (m *Params) Size() (n int) { n += 1 + l + sovParams(uint64(l)) } } + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.PendingChangeExpiry) + n += 1 + l + sovParams(uint64(l)) return n } @@ -231,6 +260,39 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PendingChangeExpiry", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.PendingChangeExpiry, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/registry/types/registry.pb.go b/x/registry/types/registry.pb.go index e25fcc9e26..cb92761e6e 100644 --- a/x/registry/types/registry.pb.go +++ b/x/registry/types/registry.pb.go @@ -8,15 +8,19 @@ import ( _ "github.com/cosmos/cosmos-proto" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/timestamppb" io "io" math "math" math_bits "math/bits" + time "time" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. @@ -359,6 +363,9 @@ type PendingRoleChange struct { Proposer string `protobuf:"bytes,4,opt,name=proposer,proto3" json:"proposer,omitempty"` // approvals is the accumulated set of addresses that have approved this change. Approvals []string `protobuf:"bytes,5,rep,name=approvals,proto3" json:"approvals,omitempty"` + // expires_at is the block time after which this pending change is no longer valid. + // Zero value means no expiry. + ExpiresAt time.Time `protobuf:"bytes,6,opt,name=expires_at,json=expiresAt,proto3,stdtime" json:"expires_at"` } func (m *PendingRoleChange) Reset() { *m = PendingRoleChange{} } @@ -429,6 +436,13 @@ func (m *PendingRoleChange) GetApprovals() []string { return nil } +func (m *PendingRoleChange) GetExpiresAt() time.Time { + if m != nil { + return m.ExpiresAt + } + return time.Time{} +} + func init() { proto.RegisterEnum("provenance.registry.v1.RegistryRole", RegistryRole_name, RegistryRole_value) proto.RegisterType((*RegistryKey)(nil), "provenance.registry.v1.RegistryKey") @@ -443,49 +457,53 @@ func init() { } var fileDescriptor_2fa7a0b4d34d0208 = []byte{ - // 662 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x94, 0xcd, 0x4e, 0xdb, 0x4c, - 0x14, 0x86, 0x63, 0x27, 0xe1, 0x23, 0x27, 0x7c, 0xd4, 0x8c, 0x80, 0x3a, 0xb4, 0x04, 0x94, 0x82, - 0x44, 0xab, 0x92, 0x08, 0x4a, 0xab, 0xaa, 0x8b, 0x4a, 0xf9, 0x19, 0x90, 0x45, 0x64, 0x47, 0x93, - 0xa4, 0x88, 0x6e, 0x2c, 0x13, 0x0f, 0xc1, 0x22, 0xf1, 0x58, 0x1e, 0x83, 0x9a, 0x4d, 0xaf, 0xa1, - 0x9b, 0x4a, 0x5d, 0xf6, 0x22, 0x7a, 0x11, 0x2c, 0x69, 0x57, 0x5d, 0xa1, 0x0a, 0x76, 0xed, 0x4d, - 0x54, 0xb6, 0x93, 0x18, 0x48, 0x51, 0xd4, 0x55, 0x77, 0xf6, 0x79, 0x9f, 0x33, 0xef, 0xf9, 0x19, - 0x0d, 0xac, 0x3a, 0x2e, 0x3b, 0xa5, 0xb6, 0x61, 0xb7, 0x68, 0xc1, 0xa5, 0x6d, 0x8b, 0x7b, 0x6e, - 0xaf, 0x70, 0xba, 0x31, 0xfc, 0xce, 0x3b, 0x2e, 0xf3, 0x18, 0x9a, 0x8f, 0xb0, 0xfc, 0x50, 0x3a, - 0xdd, 0x58, 0x98, 0x6d, 0xb3, 0x36, 0x0b, 0x90, 0x82, 0xff, 0x15, 0xd2, 0x0b, 0x99, 0x16, 0xe3, - 0x5d, 0xc6, 0xf5, 0x50, 0x08, 0x7f, 0x42, 0x29, 0x57, 0x83, 0x34, 0xe9, 0xe7, 0xef, 0xd2, 0x1e, - 0x9a, 0x83, 0x09, 0xfb, 0xd0, 0xd3, 0x2d, 0x53, 0x16, 0x96, 0x85, 0xb5, 0x14, 0x49, 0xda, 0x87, - 0x9e, 0x62, 0xa2, 0x15, 0x98, 0x36, 0x38, 0xa7, 0x9e, 0xde, 0xea, 0x18, 0x9c, 0xfb, 0xb2, 0x18, - 0xc8, 0x53, 0x41, 0xb4, 0xec, 0x07, 0x15, 0xf3, 0x55, 0xe2, 0xd3, 0xe7, 0xa5, 0x58, 0xee, 0xab, - 0x00, 0xff, 0x0f, 0x8e, 0xc4, 0xb6, 0xe7, 0xf6, 0xd0, 0x73, 0x88, 0x1f, 0xd3, 0x5e, 0x70, 0x62, - 0x7a, 0xf3, 0x51, 0xfe, 0xcf, 0xa5, 0xe7, 0xaf, 0x95, 0x41, 0x7c, 0x1e, 0xbd, 0x86, 0xa4, 0xcb, - 0x3a, 0x94, 0xcb, 0xe2, 0x72, 0x7c, 0x2d, 0xbd, 0x99, 0xbb, 0x33, 0xd1, 0x87, 0x02, 0xa7, 0x52, - 0xe2, 0xec, 0x62, 0x29, 0x46, 0xc2, 0x34, 0xa4, 0xc0, 0xcc, 0x00, 0x8b, 0xea, 0x8e, 0xfb, 0x75, - 0x97, 0x16, 0x7f, 0x5e, 0x2c, 0x65, 0x06, 0x62, 0xbf, 0xfc, 0xa7, 0xac, 0x6b, 0x79, 0xb4, 0xeb, - 0x78, 0x3d, 0x72, 0xef, 0x96, 0x94, 0x7b, 0x0f, 0x10, 0xb9, 0xa0, 0x97, 0x90, 0xf0, 0x1d, 0x82, - 0x86, 0xa6, 0x37, 0x57, 0xc6, 0x35, 0xe4, 0x67, 0x92, 0x20, 0x03, 0xbd, 0x80, 0x94, 0x61, 0x9a, - 0x2e, 0xe5, 0xbc, 0xdf, 0x56, 0xaa, 0x24, 0x7f, 0xfb, 0xb2, 0x3e, 0xdb, 0x5f, 0x49, 0x31, 0xd4, - 0xea, 0x9e, 0x6b, 0xd9, 0x6d, 0x12, 0xa1, 0x03, 0xff, 0xa6, 0x63, 0x1a, 0x1e, 0xfd, 0x07, 0xfe, - 0x1f, 0x45, 0x98, 0xa9, 0x51, 0xdb, 0xf4, 0xc3, 0xac, 0x43, 0xcb, 0x47, 0x86, 0xdd, 0xa6, 0x68, - 0x1a, 0xc4, 0xe1, 0x45, 0x11, 0x2d, 0x73, 0xb0, 0x67, 0xf1, 0x2f, 0xf7, 0xbc, 0x0b, 0x53, 0x7e, - 0x71, 0xfa, 0x49, 0xd0, 0x1d, 0x97, 0xe3, 0xe3, 0xd7, 0x1d, 0x0e, 0xa2, 0xbf, 0xee, 0xb4, 0x3b, - 0x8c, 0x70, 0xb4, 0x05, 0x93, 0x8e, 0xcb, 0x1c, 0xc6, 0xa9, 0x2b, 0x27, 0x82, 0x5d, 0xdf, 0xdd, - 0xe0, 0x90, 0x0c, 0xe6, 0xe2, 0xf8, 0x7e, 0x46, 0x87, 0xcb, 0xc9, 0xb1, 0x73, 0x19, 0xa0, 0x4f, - 0x7e, 0x89, 0x30, 0x75, 0x7d, 0xcc, 0x68, 0x11, 0x32, 0x04, 0xef, 0x28, 0xf5, 0x06, 0xd9, 0xd7, - 0x89, 0x56, 0xc5, 0x7a, 0x53, 0xad, 0xd7, 0x70, 0x59, 0xd9, 0x56, 0x70, 0x45, 0x8a, 0xa1, 0x05, - 0x98, 0xbf, 0x29, 0xd7, 0x31, 0x79, 0xa3, 0x94, 0x31, 0x91, 0x84, 0xd1, 0xd4, 0x7a, 0xb3, 0x34, - 0x94, 0x45, 0xf4, 0x10, 0xe4, 0x9b, 0x72, 0x59, 0x53, 0x1b, 0x44, 0xab, 0x56, 0x31, 0x91, 0xe2, - 0xe8, 0x01, 0xdc, 0xbf, 0xa5, 0x36, 0xeb, 0x0d, 0xad, 0xa2, 0x14, 0x55, 0x29, 0x31, 0xea, 0x5a, - 0xd2, 0x08, 0xd1, 0xf6, 0x30, 0x91, 0x92, 0xa3, 0xc7, 0x6a, 0x44, 0xd9, 0x51, 0xd4, 0x62, 0x43, - 0x23, 0xd2, 0xc4, 0xa8, 0x5a, 0x55, 0xb0, 0xaa, 0x6b, 0x7b, 0x2a, 0x26, 0xd2, 0x7f, 0x68, 0x0d, - 0x56, 0x6e, 0x77, 0x53, 0x6e, 0x12, 0x5c, 0xd1, 0x6b, 0x45, 0xd2, 0xd8, 0xd7, 0xb7, 0x35, 0x12, - 0xf0, 0xd2, 0x24, 0x7a, 0x0c, 0xab, 0xe3, 0x48, 0xac, 0x6a, 0x0d, 0x2c, 0xa5, 0x50, 0x06, 0xe6, - 0x6e, 0xa2, 0xb5, 0x2a, 0xae, 0xec, 0x60, 0x2c, 0x41, 0xe9, 0xf8, 0xec, 0x32, 0x2b, 0x9c, 0x5f, - 0x66, 0x85, 0x1f, 0x97, 0x59, 0xe1, 0xc3, 0x55, 0x36, 0x76, 0x7e, 0x95, 0x8d, 0x7d, 0xbf, 0xca, - 0xc6, 0x20, 0x63, 0xb1, 0x3b, 0xae, 0x4b, 0x4d, 0x78, 0xbb, 0xd5, 0xb6, 0xbc, 0xa3, 0x93, 0x83, - 0x7c, 0x8b, 0x75, 0x0b, 0x11, 0xb4, 0x6e, 0xb1, 0x6b, 0x7f, 0x85, 0x77, 0xd1, 0x6b, 0xeb, 0xf5, - 0x1c, 0xca, 0x0f, 0x26, 0x82, 0xf7, 0xf1, 0xd9, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xea, 0xe0, - 0xf7, 0xfe, 0x91, 0x05, 0x00, 0x00, + // 721 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x54, 0x4f, 0x4f, 0xdb, 0x48, + 0x1c, 0x8d, 0x9d, 0x3f, 0x4b, 0x26, 0x2c, 0x6b, 0x46, 0xc0, 0x3a, 0xd9, 0x25, 0x41, 0x59, 0x90, + 0xd8, 0xd5, 0xe2, 0x08, 0x96, 0xad, 0xaa, 0x1e, 0x2a, 0xe5, 0xcf, 0x80, 0x2c, 0xa2, 0x38, 0x9a, + 0x24, 0x45, 0xf4, 0x62, 0x99, 0x78, 0x30, 0x16, 0x89, 0xc7, 0xf2, 0x0c, 0x88, 0x5c, 0xfa, 0x19, + 0x38, 0xf6, 0xd8, 0x0f, 0xd1, 0x0f, 0xc1, 0x91, 0xf6, 0x54, 0xf5, 0x40, 0x2b, 0xb8, 0xb5, 0x5f, + 0xa2, 0xb2, 0x1d, 0x27, 0x40, 0x8a, 0xa2, 0x9e, 0x7a, 0xf3, 0xfc, 0xde, 0xfb, 0xcd, 0xfb, 0xbd, + 0x79, 0x33, 0x06, 0x6b, 0xae, 0x47, 0xcf, 0x88, 0x63, 0x38, 0x5d, 0x52, 0xf2, 0x88, 0x65, 0x33, + 0xee, 0x0d, 0x4a, 0x67, 0x9b, 0xa3, 0x6f, 0xc5, 0xf5, 0x28, 0xa7, 0x70, 0x69, 0x4c, 0x53, 0x46, + 0xd0, 0xd9, 0x66, 0x6e, 0xc1, 0xa2, 0x16, 0x0d, 0x28, 0x25, 0xff, 0x2b, 0x64, 0xe7, 0xb2, 0x5d, + 0xca, 0xfa, 0x94, 0xe9, 0x21, 0x10, 0x2e, 0x86, 0x50, 0xc1, 0xa2, 0xd4, 0xea, 0x91, 0x52, 0xb0, + 0x3a, 0x3c, 0x3d, 0x2a, 0x71, 0xbb, 0x4f, 0x18, 0x37, 0xfa, 0x6e, 0x48, 0x28, 0x36, 0x41, 0x06, + 0x0f, 0x05, 0xf6, 0xc8, 0x00, 0x2e, 0x82, 0x94, 0x73, 0xc4, 0x75, 0xdb, 0x94, 0x85, 0x15, 0x61, + 0x3d, 0x8d, 0x93, 0xce, 0x11, 0x57, 0x4d, 0xb8, 0x0a, 0xe6, 0x0c, 0xc6, 0x08, 0xd7, 0xbb, 0x3d, + 0x83, 0x31, 0x1f, 0x16, 0x03, 0x78, 0x36, 0xa8, 0x56, 0xfd, 0xa2, 0x6a, 0x3e, 0x4b, 0xbc, 0x7e, + 0x53, 0x88, 0x15, 0xdf, 0x09, 0xe0, 0xd7, 0x68, 0x4b, 0xe4, 0x70, 0x6f, 0x00, 0xff, 0x07, 0xf1, + 0x13, 0x32, 0x08, 0x76, 0xcc, 0x6c, 0xfd, 0xa5, 0x7c, 0xdf, 0x9b, 0x72, 0x67, 0x0c, 0xec, 0xf3, + 0xe1, 0x73, 0x90, 0xf4, 0x68, 0x8f, 0x30, 0x59, 0x5c, 0x89, 0xaf, 0x67, 0xb6, 0x8a, 0x8f, 0x36, + 0xfa, 0xa4, 0x40, 0xa9, 0x92, 0xb8, 0xbc, 0x2e, 0xc4, 0x70, 0xd8, 0x06, 0x55, 0x30, 0x1f, 0xd1, + 0xc6, 0x73, 0xc7, 0xfd, 0xb9, 0x2b, 0xcb, 0x5f, 0xae, 0x0b, 0xd9, 0x08, 0x1c, 0x8e, 0xff, 0x2f, + 0xed, 0xdb, 0x9c, 0xf4, 0x5d, 0x3e, 0xc0, 0xbf, 0x3d, 0x80, 0x8a, 0xaf, 0x00, 0x18, 0xab, 0xc0, + 0xa7, 0x20, 0xe1, 0x2b, 0x04, 0x86, 0xe6, 0xb6, 0x56, 0xa7, 0x19, 0xf2, 0x3b, 0x71, 0xd0, 0x01, + 0x9f, 0x80, 0xb4, 0x61, 0x9a, 0x1e, 0x61, 0x6c, 0x68, 0x2b, 0x5d, 0x91, 0xdf, 0xbf, 0xdd, 0x58, + 0x18, 0x66, 0x56, 0x0e, 0xb1, 0x16, 0xf7, 0x6c, 0xc7, 0xc2, 0x63, 0x6a, 0xa4, 0xdf, 0x71, 0x4d, + 0x83, 0x93, 0x9f, 0xa0, 0xff, 0x51, 0x04, 0xf3, 0x4d, 0xe2, 0x98, 0x7e, 0x99, 0xf6, 0x48, 0xf5, + 0xd8, 0x70, 0x2c, 0x02, 0xe7, 0x80, 0x38, 0xba, 0x28, 0xa2, 0x6d, 0x46, 0x39, 0x8b, 0x3f, 0x98, + 0xf3, 0x1e, 0x98, 0xf5, 0x87, 0xd3, 0x4f, 0x03, 0x77, 0x4c, 0x8e, 0x4f, 0x8f, 0x3b, 0x3c, 0x88, + 0x61, 0xdc, 0x19, 0x6f, 0x54, 0x61, 0x70, 0x1b, 0xcc, 0xb8, 0x1e, 0x75, 0x29, 0x23, 0x9e, 0x9c, + 0x08, 0xb2, 0x7e, 0xdc, 0xe0, 0x88, 0x19, 0x9c, 0x8b, 0xeb, 0xeb, 0x19, 0x3d, 0x26, 0x27, 0xa7, + 0x9e, 0x4b, 0x44, 0x85, 0x55, 0x00, 0xc8, 0xb9, 0x6b, 0x7b, 0x84, 0xe9, 0x06, 0x97, 0x53, 0x81, + 0xf1, 0x9c, 0x12, 0xbe, 0x39, 0x25, 0x7a, 0x73, 0x4a, 0x3b, 0x7a, 0x73, 0x95, 0x19, 0x7f, 0xe0, + 0x8b, 0x4f, 0x05, 0x01, 0xa7, 0x87, 0x7d, 0x65, 0xfe, 0xcf, 0x57, 0x11, 0xcc, 0xde, 0xcd, 0x0a, + 0x2e, 0x83, 0x2c, 0x46, 0xbb, 0x6a, 0xab, 0x8d, 0x0f, 0x74, 0xac, 0xd5, 0x91, 0xde, 0x69, 0xb4, + 0x9a, 0xa8, 0xaa, 0xee, 0xa8, 0xa8, 0x26, 0xc5, 0x60, 0x0e, 0x2c, 0xdd, 0x87, 0x5b, 0x08, 0xbf, + 0x50, 0xab, 0x08, 0x4b, 0xc2, 0x64, 0x6b, 0xab, 0x53, 0x19, 0xc1, 0x22, 0xfc, 0x13, 0xc8, 0xf7, + 0xe1, 0xaa, 0xd6, 0x68, 0x63, 0xad, 0x5e, 0x47, 0x58, 0x8a, 0xc3, 0x3f, 0xc0, 0xef, 0x0f, 0xd0, + 0x4e, 0xab, 0xad, 0xd5, 0xd4, 0x72, 0x43, 0x4a, 0x4c, 0xaa, 0x56, 0x34, 0x8c, 0xb5, 0x7d, 0x84, + 0xa5, 0xe4, 0xe4, 0xb6, 0x1a, 0x56, 0x77, 0xd5, 0x46, 0xb9, 0xad, 0x61, 0x29, 0x35, 0x89, 0xd6, + 0x55, 0xd4, 0xd0, 0xb5, 0xfd, 0x06, 0xc2, 0xd2, 0x2f, 0x70, 0x1d, 0xac, 0x3e, 0x74, 0x53, 0xed, + 0x60, 0x54, 0xd3, 0x9b, 0x65, 0xdc, 0x3e, 0xd0, 0x77, 0x34, 0x1c, 0xf0, 0xa5, 0x19, 0xf8, 0x37, + 0x58, 0x9b, 0xc6, 0x44, 0x0d, 0xad, 0x8d, 0xa4, 0x34, 0xcc, 0x82, 0xc5, 0xfb, 0xd4, 0x66, 0x1d, + 0xd5, 0x76, 0x11, 0x92, 0x40, 0xe5, 0xe4, 0xf2, 0x26, 0x2f, 0x5c, 0xdd, 0xe4, 0x85, 0xcf, 0x37, + 0x79, 0xe1, 0xe2, 0x36, 0x1f, 0xbb, 0xba, 0xcd, 0xc7, 0x3e, 0xdc, 0xe6, 0x63, 0x20, 0x6b, 0xd3, + 0x47, 0xee, 0x5c, 0x53, 0x78, 0xb9, 0x6d, 0xd9, 0xfc, 0xf8, 0xf4, 0x50, 0xe9, 0xd2, 0x7e, 0x69, + 0x4c, 0xda, 0xb0, 0xe9, 0x9d, 0x55, 0xe9, 0x7c, 0xfc, 0x4f, 0xe7, 0x03, 0x97, 0xb0, 0xc3, 0x54, + 0x70, 0x07, 0xfe, 0xfb, 0x16, 0x00, 0x00, 0xff, 0xff, 0x76, 0x19, 0x01, 0x7c, 0xf7, 0x05, 0x00, + 0x00, } func (m *RegistryKey) Marshal() (dAtA []byte, err error) { @@ -675,6 +693,14 @@ func (m *PendingRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.ExpiresAt, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.ExpiresAt):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintRegistry(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0x32 if len(m.Approvals) > 0 { for iNdEx := len(m.Approvals) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.Approvals[iNdEx]) @@ -844,6 +870,8 @@ func (m *PendingRoleChange) Size() (n int) { n += 1 + l + sovRegistry(uint64(l)) } } + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.ExpiresAt) + n += 1 + l + sovRegistry(uint64(l)) return n } @@ -1516,6 +1544,39 @@ func (m *PendingRoleChange) Unmarshal(dAtA []byte) error { } m.Approvals = append(m.Approvals, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ExpiresAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRegistry + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRegistry + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRegistry + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.ExpiresAt, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRegistry(dAtA[iNdEx:]) diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index 632b41357f..e80580bbb6 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -905,6 +905,99 @@ func (m *MsgApproveRoleChangeResponse) GetApplied() bool { return false } +// MsgCancelRoleChange cancels an open pending role change. +// Only the original proposer may cancel. +type MsgCancelRoleChange struct { + // signer must be the original proposer of the pending change. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // change_id is the identifier of the pending change to cancel. + ChangeId string `protobuf:"bytes,2,opt,name=change_id,json=changeId,proto3" json:"change_id,omitempty"` +} + +func (m *MsgCancelRoleChange) Reset() { *m = MsgCancelRoleChange{} } +func (m *MsgCancelRoleChange) String() string { return proto.CompactTextString(m) } +func (*MsgCancelRoleChange) ProtoMessage() {} +func (*MsgCancelRoleChange) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{16} +} +func (m *MsgCancelRoleChange) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCancelRoleChange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCancelRoleChange.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCancelRoleChange) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCancelRoleChange.Merge(m, src) +} +func (m *MsgCancelRoleChange) XXX_Size() int { + return m.Size() +} +func (m *MsgCancelRoleChange) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCancelRoleChange.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCancelRoleChange proto.InternalMessageInfo + +func (m *MsgCancelRoleChange) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgCancelRoleChange) GetChangeId() string { + if m != nil { + return m.ChangeId + } + return "" +} + +// MsgCancelRoleChangeResponse defines the response for CancelRoleChange. +type MsgCancelRoleChangeResponse struct { +} + +func (m *MsgCancelRoleChangeResponse) Reset() { *m = MsgCancelRoleChangeResponse{} } +func (m *MsgCancelRoleChangeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgCancelRoleChangeResponse) ProtoMessage() {} +func (*MsgCancelRoleChangeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{17} +} +func (m *MsgCancelRoleChangeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgCancelRoleChangeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgCancelRoleChangeResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgCancelRoleChangeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgCancelRoleChangeResponse.Merge(m, src) +} +func (m *MsgCancelRoleChangeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgCancelRoleChangeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgCancelRoleChangeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgCancelRoleChangeResponse proto.InternalMessageInfo + // MsgCreateRegistryClass creates a new registry class defining asset class-level authorization // rules for role updates within an NFT collection. type MsgCreateRegistryClass struct { @@ -926,7 +1019,7 @@ func (m *MsgCreateRegistryClass) Reset() { *m = MsgCreateRegistryClass{} func (m *MsgCreateRegistryClass) String() string { return proto.CompactTextString(m) } func (*MsgCreateRegistryClass) ProtoMessage() {} func (*MsgCreateRegistryClass) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{16} + return fileDescriptor_afab3f18b6d8353c, []int{18} } func (m *MsgCreateRegistryClass) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -998,7 +1091,7 @@ func (m *MsgCreateRegistryClassResponse) Reset() { *m = MsgCreateRegistr func (m *MsgCreateRegistryClassResponse) String() string { return proto.CompactTextString(m) } func (*MsgCreateRegistryClassResponse) ProtoMessage() {} func (*MsgCreateRegistryClassResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{17} + return fileDescriptor_afab3f18b6d8353c, []int{19} } func (m *MsgCreateRegistryClassResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1044,7 +1137,7 @@ func (m *MsgUpdateRegistryClassRoleAuthorization) Reset() { func (m *MsgUpdateRegistryClassRoleAuthorization) String() string { return proto.CompactTextString(m) } func (*MsgUpdateRegistryClassRoleAuthorization) ProtoMessage() {} func (*MsgUpdateRegistryClassRoleAuthorization) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{18} + return fileDescriptor_afab3f18b6d8353c, []int{20} } func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1107,7 +1200,7 @@ func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) String() string { } func (*MsgUpdateRegistryClassRoleAuthorizationResponse) ProtoMessage() {} func (*MsgUpdateRegistryClassRoleAuthorizationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{19} + return fileDescriptor_afab3f18b6d8353c, []int{21} } func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1148,7 +1241,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{20} + return fileDescriptor_afab3f18b6d8353c, []int{22} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1199,7 +1292,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{21} + return fileDescriptor_afab3f18b6d8353c, []int{23} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1245,6 +1338,8 @@ func init() { proto.RegisterType((*MsgProposeRoleChangeResponse)(nil), "provenance.registry.v1.MsgProposeRoleChangeResponse") proto.RegisterType((*MsgApproveRoleChange)(nil), "provenance.registry.v1.MsgApproveRoleChange") proto.RegisterType((*MsgApproveRoleChangeResponse)(nil), "provenance.registry.v1.MsgApproveRoleChangeResponse") + proto.RegisterType((*MsgCancelRoleChange)(nil), "provenance.registry.v1.MsgCancelRoleChange") + proto.RegisterType((*MsgCancelRoleChangeResponse)(nil), "provenance.registry.v1.MsgCancelRoleChangeResponse") proto.RegisterType((*MsgCreateRegistryClass)(nil), "provenance.registry.v1.MsgCreateRegistryClass") proto.RegisterType((*MsgCreateRegistryClassResponse)(nil), "provenance.registry.v1.MsgCreateRegistryClassResponse") proto.RegisterType((*MsgUpdateRegistryClassRoleAuthorization)(nil), "provenance.registry.v1.MsgUpdateRegistryClassRoleAuthorization") @@ -1256,73 +1351,75 @@ func init() { func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 1053 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x98, 0x41, 0x6f, 0x1b, 0x45, - 0x14, 0xc7, 0x33, 0xb6, 0x93, 0x26, 0xcf, 0x69, 0x4a, 0x37, 0x69, 0xb2, 0x59, 0xa8, 0x63, 0x39, - 0x09, 0x35, 0xa1, 0xb1, 0x9b, 0xd0, 0x56, 0x51, 0x85, 0x40, 0x49, 0x54, 0xaa, 0xa8, 0x32, 0x8a, - 0xb6, 0xe4, 0x82, 0x90, 0xcc, 0xc6, 0x1e, 0x6d, 0x56, 0x8e, 0x77, 0x56, 0x33, 0x93, 0x50, 0x57, - 0x42, 0x42, 0x9c, 0xb8, 0x20, 0xc1, 0x89, 0x13, 0xdf, 0x21, 0x07, 0xbe, 0x02, 0x52, 0x8f, 0x11, - 0x27, 0xb8, 0x54, 0x28, 0x39, 0x54, 0xe2, 0x03, 0xc0, 0x15, 0xed, 0xec, 0xee, 0x78, 0xd7, 0xf6, - 0xae, 0x37, 0xad, 0x5a, 0x24, 0x7a, 0xf3, 0xee, 0xfc, 0x66, 0xde, 0xff, 0xff, 0xfc, 0x66, 0xe6, - 0x69, 0x61, 0xc1, 0xa1, 0xe4, 0x18, 0xdb, 0x86, 0xdd, 0xc0, 0x55, 0x8a, 0x4d, 0x8b, 0x71, 0xda, - 0xa9, 0x1e, 0xaf, 0x55, 0xf9, 0xe3, 0x8a, 0x43, 0x09, 0x27, 0xca, 0x6c, 0x17, 0xa8, 0x04, 0x40, - 0xe5, 0x78, 0x4d, 0x9b, 0x31, 0x89, 0x49, 0x04, 0x52, 0x75, 0x7f, 0x79, 0xb4, 0x36, 0xdf, 0x20, - 0xac, 0x4d, 0x58, 0xdd, 0x1b, 0xf0, 0x1e, 0xfc, 0xa1, 0x39, 0xef, 0xa9, 0xda, 0x66, 0xa6, 0x1b, - 0xa0, 0xcd, 0x4c, 0x7f, 0x60, 0x39, 0x46, 0x82, 0x8c, 0xe6, 0x61, 0x2b, 0x31, 0x98, 0x71, 0xc4, - 0x0f, 0x08, 0xb5, 0x9e, 0x18, 0xdc, 0x22, 0xb6, 0xcf, 0x2e, 0xc6, 0xb0, 0x8e, 0x41, 0x8d, 0xb6, - 0x2f, 0xa8, 0xf4, 0x63, 0x06, 0xa6, 0x6a, 0xcc, 0xd4, 0xc5, 0x38, 0xa6, 0x9f, 0x7e, 0xf2, 0x99, - 0x72, 0x0b, 0xc6, 0x98, 0x65, 0xda, 0x98, 0xaa, 0xa8, 0x88, 0xca, 0x13, 0x5b, 0xea, 0x6f, 0xbf, - 0xac, 0xce, 0xf8, 0x2e, 0x36, 0x9b, 0x4d, 0x8a, 0x19, 0x7b, 0xc4, 0xa9, 0x65, 0x9b, 0xba, 0xcf, - 0x29, 0x77, 0x20, 0xdb, 0xc2, 0x1d, 0x35, 0x53, 0x44, 0xe5, 0xfc, 0xfa, 0x62, 0x65, 0x70, 0xb2, - 0x2a, 0xba, 0xff, 0xfb, 0x21, 0xee, 0xe8, 0x2e, 0xaf, 0x7c, 0x04, 0xa3, 0x94, 0x1c, 0x62, 0xa6, - 0x66, 0x8b, 0xd9, 0x72, 0x7e, 0xbd, 0x14, 0x3b, 0xd1, 0x85, 0xee, 0xdb, 0x9c, 0x76, 0xb6, 0x72, - 0x4f, 0x9f, 0x2d, 0x8c, 0xe8, 0xde, 0x34, 0x65, 0x07, 0xae, 0x06, 0x58, 0xbd, 0x71, 0x68, 0x30, - 0x56, 0xb7, 0x9a, 0x6a, 0x4e, 0x68, 0xbe, 0xfe, 0xd7, 0xb3, 0x85, 0xf9, 0x60, 0x70, 0xdb, 0x1d, - 0xdb, 0x69, 0xde, 0x24, 0x6d, 0x8b, 0xe3, 0xb6, 0xc3, 0x3b, 0xfa, 0x95, 0x9e, 0xa1, 0x7b, 0xf9, - 0x6f, 0x9f, 0x9f, 0xac, 0xf8, 0x76, 0x4a, 0x2a, 0xcc, 0x46, 0x53, 0xa2, 0x63, 0xe6, 0x10, 0x9b, - 0xe1, 0xd2, 0xdf, 0x08, 0x26, 0x6b, 0xcc, 0x7c, 0x40, 0x0d, 0x9b, 0xbb, 0xaa, 0x5e, 0x5f, 0xae, - 0x36, 0x20, 0xe7, 0x9a, 0x56, 0xb3, 0x45, 0x54, 0x9e, 0x5a, 0x5f, 0x1a, 0x36, 0xcf, 0x15, 0xa7, - 0x8b, 0x19, 0xca, 0x5d, 0x98, 0x30, 0x3c, 0x25, 0x98, 0xa9, 0xb9, 0x62, 0x36, 0x51, 0x65, 0x17, - 0x8d, 0xa6, 0x64, 0x16, 0x66, 0xc2, 0xbe, 0x65, 0x42, 0xfe, 0x41, 0x70, 0x59, 0xe4, 0xea, 0x98, - 0xb4, 0xf0, 0x1b, 0x95, 0x91, 0x39, 0xb8, 0x16, 0x31, 0x2e, 0x53, 0xf2, 0x1d, 0x82, 0xb7, 0x6a, - 0xcc, 0xdc, 0xb3, 0xe9, 0x7f, 0xb0, 0xa7, 0xa2, 0x1a, 0x35, 0x50, 0x7b, 0x95, 0x48, 0x99, 0x3f, - 0x23, 0xdf, 0x80, 0xb7, 0xc0, 0xd6, 0xd1, 0x61, 0x6b, 0xcf, 0x69, 0x1a, 0xfc, 0x45, 0xfe, 0xc1, - 0xfb, 0x70, 0x09, 0xdb, 0x9c, 0x5a, 0x98, 0xa9, 0x19, 0xb1, 0x95, 0x97, 0x87, 0xe9, 0x0d, 0xef, - 0xe6, 0x60, 0x6e, 0x54, 0xfb, 0x02, 0x5c, 0x1f, 0x28, 0x4f, 0x1a, 0x38, 0x45, 0x90, 0xaf, 0x31, - 0xf3, 0x11, 0x16, 0x15, 0xc9, 0x5e, 0x5f, 0xe1, 0x3d, 0x84, 0x49, 0xb7, 0x8c, 0xea, 0x47, 0x42, - 0x4f, 0xaa, 0xd3, 0xcb, 0x93, 0xee, 0xfb, 0xcd, 0x53, 0xf9, 0xa6, 0xc7, 0xf3, 0x35, 0x98, 0x0e, - 0x39, 0x92, 0x4e, 0xff, 0x40, 0x62, 0xf7, 0xed, 0x52, 0xe2, 0x10, 0x26, 0x8a, 0x6d, 0xfb, 0xc0, - 0xb0, 0x4d, 0xfc, 0x7f, 0xb0, 0xbc, 0x07, 0xef, 0x0c, 0xb2, 0x16, 0x78, 0x57, 0xde, 0x86, 0x89, - 0x86, 0x78, 0xe3, 0x9e, 0xed, 0xc2, 0xa5, 0x3e, 0xee, 0xbd, 0xd8, 0x69, 0x2a, 0x2a, 0x5c, 0x32, - 0x1c, 0xe7, 0xd0, 0xc2, 0x4d, 0xe1, 0x68, 0x5c, 0x0f, 0x1e, 0x4b, 0x54, 0x64, 0x6c, 0xd3, 0x11, - 0x02, 0x5f, 0x2a, 0x63, 0x11, 0x01, 0x99, 0xa8, 0x80, 0xa8, 0x95, 0x0d, 0x61, 0xa5, 0x2f, 0xa6, - 0xb4, 0x12, 0x52, 0x8b, 0xa2, 0x6a, 0x7f, 0xcd, 0x88, 0x1b, 0x67, 0x9b, 0x62, 0x51, 0xe0, 0xa1, - 0xab, 0xe9, 0x05, 0x04, 0xaf, 0x0c, 0xba, 0x15, 0x3d, 0xe1, 0xbd, 0xd7, 0x9e, 0xb2, 0x04, 0x53, - 0x06, 0x63, 0x98, 0x77, 0xc1, 0xac, 0x00, 0x27, 0xc5, 0xdb, 0x80, 0xda, 0x00, 0x68, 0x1b, 0x96, - 0xcd, 0x0d, 0xcb, 0xd5, 0x91, 0x1b, 0xa2, 0x23, 0xc4, 0x2a, 0x5f, 0xc2, 0xb4, 0xa8, 0x9b, 0x48, - 0x7b, 0xc2, 0xd4, 0x51, 0x51, 0x3e, 0xef, 0x25, 0x95, 0xcf, 0x66, 0x78, 0x86, 0x5f, 0x45, 0x0a, - 0xed, 0x1d, 0xe8, 0x29, 0xa6, 0x22, 0x14, 0x06, 0xa7, 0x31, 0x7c, 0x81, 0xdf, 0x70, 0x8f, 0x44, - 0xff, 0x28, 0x09, 0x23, 0xbd, 0x6b, 0xbf, 0xe2, 0xd4, 0xc7, 0xa4, 0x26, 0xfb, 0x8a, 0x52, 0xb3, - 0x06, 0xd5, 0x94, 0xbe, 0x65, 0xae, 0x7e, 0x42, 0x70, 0x45, 0xce, 0xd9, 0x15, 0x4d, 0xa3, 0xb8, - 0x3a, 0x3d, 0x98, 0x77, 0x86, 0xa6, 0xa5, 0x8b, 0x2a, 0x1f, 0xc2, 0x98, 0xd7, 0x76, 0xfa, 0x47, - 0x4f, 0x21, 0xce, 0xa0, 0x17, 0xc7, 0x77, 0xe5, 0xcf, 0xb9, 0x37, 0xe5, 0x3a, 0xe9, 0xae, 0x56, - 0x9a, 0x87, 0xb9, 0x1e, 0x61, 0x81, 0xe8, 0xf5, 0xef, 0x01, 0xb2, 0x35, 0x66, 0x2a, 0x18, 0xf2, - 0xe1, 0x9e, 0xf6, 0xdd, 0xb8, 0x78, 0xd1, 0x46, 0x4f, 0xab, 0xa4, 0xe3, 0xe4, 0x9e, 0xae, 0xc3, - 0x44, 0xb7, 0x19, 0x5c, 0x4a, 0x98, 0x2c, 0x29, 0xed, 0x66, 0x1a, 0x4a, 0x06, 0xd8, 0x07, 0x08, - 0x35, 0x57, 0xcb, 0x89, 0xf2, 0x02, 0x4c, 0x5b, 0x4d, 0x85, 0xc9, 0x18, 0x2d, 0xb8, 0x1c, 0xed, - 0x56, 0xca, 0x09, 0xf3, 0x23, 0xa4, 0x76, 0x2b, 0x2d, 0x29, 0x83, 0x3d, 0x01, 0x65, 0x40, 0xcf, - 0xb1, 0x3a, 0x34, 0xef, 0x61, 0x5c, 0xbb, 0x73, 0x21, 0x5c, 0xc6, 0xfe, 0x02, 0xc6, 0x65, 0xbb, - 0xb0, 0x98, 0xb0, 0x44, 0x00, 0x69, 0xef, 0xa7, 0x80, 0xe4, 0xea, 0x5f, 0xc1, 0xd5, 0xfe, 0x2b, - 0x3a, 0xe9, 0xdf, 0xee, 0xa3, 0xb5, 0xdb, 0x17, 0xa1, 0xc3, 0x81, 0xfb, 0x6f, 0xba, 0xa4, 0xc0, - 0x7d, 0x74, 0x62, 0xe0, 0xf8, 0x1b, 0xed, 0x6b, 0x98, 0x1e, 0x74, 0x67, 0x25, 0x6d, 0xa2, 0x01, - 0xbc, 0x76, 0xf7, 0x62, 0xbc, 0x0c, 0x7f, 0x82, 0x60, 0x29, 0xd5, 0x49, 0xfe, 0x71, 0x52, 0x95, - 0xa6, 0x58, 0x40, 0x7b, 0xf0, 0x92, 0x0b, 0x48, 0xc9, 0x07, 0x30, 0x19, 0x39, 0x4f, 0x6f, 0x0c, - 0x5d, 0xd8, 0x03, 0xb5, 0x6a, 0x4a, 0x30, 0x88, 0xa4, 0x8d, 0x7e, 0xf3, 0xfc, 0x64, 0x05, 0x6d, - 0xb5, 0x9e, 0x9e, 0x15, 0xd0, 0xe9, 0x59, 0x01, 0xfd, 0x79, 0x56, 0x40, 0x3f, 0x9c, 0x17, 0x46, - 0x4e, 0xcf, 0x0b, 0x23, 0xbf, 0x9f, 0x17, 0x46, 0x60, 0xde, 0x22, 0x31, 0x6b, 0xee, 0xa2, 0xcf, - 0x6f, 0x9b, 0x16, 0x3f, 0x38, 0xda, 0xaf, 0x34, 0x48, 0xbb, 0xda, 0x85, 0x56, 0x2d, 0x12, 0x7a, - 0xaa, 0x3e, 0xee, 0x7e, 0x56, 0xe0, 0x1d, 0x07, 0xb3, 0xfd, 0x31, 0xf1, 0x4d, 0xe1, 0x83, 0x7f, - 0x03, 0x00, 0x00, 0xff, 0xff, 0xfd, 0x49, 0xd0, 0x16, 0x50, 0x11, 0x00, 0x00, + // 1085 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x98, 0xcf, 0x4f, 0x1b, 0x47, + 0x14, 0xc7, 0x19, 0x6c, 0x08, 0x3c, 0x13, 0x92, 0x2c, 0x04, 0x96, 0x4d, 0x31, 0x96, 0x81, 0xc6, + 0xa5, 0xc1, 0x0e, 0xe4, 0x87, 0x50, 0x54, 0xb5, 0x02, 0x94, 0x46, 0x28, 0x72, 0x85, 0x36, 0xe5, + 0x52, 0x55, 0x72, 0x17, 0x7b, 0xb4, 0xac, 0x6c, 0xef, 0x6c, 0x67, 0x06, 0x1a, 0x47, 0xaa, 0x54, + 0xb5, 0x97, 0x1e, 0xdb, 0x53, 0x4f, 0xfd, 0x1f, 0x38, 0xf4, 0x5f, 0xa8, 0x94, 0x23, 0xea, 0xa9, + 0xbd, 0x44, 0x15, 0x1c, 0x22, 0xf5, 0x0f, 0x68, 0xaf, 0xd5, 0xce, 0xee, 0x8e, 0x77, 0xfd, 0x63, + 0xbd, 0x24, 0x0a, 0x95, 0x9a, 0x9b, 0x77, 0xe7, 0x33, 0xf3, 0xbe, 0xdf, 0xb7, 0x6f, 0x66, 0x9e, + 0x0c, 0x0b, 0x0e, 0x25, 0x47, 0xd8, 0x36, 0xec, 0x2a, 0x2e, 0x51, 0x6c, 0x5a, 0x8c, 0xd3, 0x56, + 0xe9, 0x68, 0xad, 0xc4, 0x9f, 0x16, 0x1d, 0x4a, 0x38, 0x51, 0x66, 0xda, 0x40, 0x31, 0x00, 0x8a, + 0x47, 0x6b, 0xda, 0xb4, 0x49, 0x4c, 0x22, 0x90, 0x92, 0xfb, 0xcb, 0xa3, 0xb5, 0xb9, 0x2a, 0x61, + 0x4d, 0xc2, 0x2a, 0xde, 0x80, 0xf7, 0xe0, 0x0f, 0xcd, 0x7a, 0x4f, 0xa5, 0x26, 0x33, 0xdd, 0x00, + 0x4d, 0x66, 0xfa, 0x03, 0xcb, 0x7d, 0x24, 0xc8, 0x68, 0x1e, 0xb6, 0xd2, 0x07, 0x33, 0x0e, 0xf9, + 0x01, 0xa1, 0xd6, 0x33, 0x83, 0x5b, 0xc4, 0xf6, 0xd9, 0xc5, 0x3e, 0xac, 0x63, 0x50, 0xa3, 0xe9, + 0x0b, 0xca, 0xff, 0x38, 0x0c, 0x93, 0x65, 0x66, 0xea, 0x62, 0x1c, 0xd3, 0x4f, 0x3e, 0xfe, 0x54, + 0xb9, 0x0d, 0xa3, 0xcc, 0x32, 0x6d, 0x4c, 0x55, 0x94, 0x43, 0x85, 0xf1, 0x2d, 0xf5, 0xb7, 0x5f, + 0x56, 0xa7, 0x7d, 0x17, 0x9b, 0xb5, 0x1a, 0xc5, 0x8c, 0x3d, 0xe1, 0xd4, 0xb2, 0x4d, 0xdd, 0xe7, + 0x94, 0x7b, 0x90, 0xaa, 0xe3, 0x96, 0x3a, 0x9c, 0x43, 0x85, 0xcc, 0xfa, 0x62, 0xb1, 0x77, 0xb2, + 0x8a, 0xba, 0xff, 0xfb, 0x31, 0x6e, 0xe9, 0x2e, 0xaf, 0x7c, 0x08, 0x23, 0x94, 0x34, 0x30, 0x53, + 0x53, 0xb9, 0x54, 0x21, 0xb3, 0x9e, 0xef, 0x3b, 0xd1, 0x85, 0x1e, 0xda, 0x9c, 0xb6, 0xb6, 0xd2, + 0xcf, 0x5f, 0x2c, 0x0c, 0xe9, 0xde, 0x34, 0x65, 0x07, 0xae, 0x05, 0x58, 0xa5, 0xda, 0x30, 0x18, + 0xab, 0x58, 0x35, 0x35, 0x2d, 0x34, 0xcf, 0xff, 0xf5, 0x62, 0x61, 0x2e, 0x18, 0xdc, 0x76, 0xc7, + 0x76, 0x6a, 0xb7, 0x48, 0xd3, 0xe2, 0xb8, 0xe9, 0xf0, 0x96, 0x7e, 0xa5, 0x63, 0xe8, 0x41, 0xe6, + 0xdb, 0x97, 0xc7, 0x2b, 0xbe, 0x9d, 0xbc, 0x0a, 0x33, 0xd1, 0x94, 0xe8, 0x98, 0x39, 0xc4, 0x66, + 0x38, 0xff, 0x37, 0x82, 0x89, 0x32, 0x33, 0x1f, 0x51, 0xc3, 0xe6, 0xae, 0xaa, 0x8b, 0xcb, 0xd5, + 0x06, 0xa4, 0x5d, 0xd3, 0x6a, 0x2a, 0x87, 0x0a, 0x93, 0xeb, 0x4b, 0x83, 0xe6, 0xb9, 0xe2, 0x74, + 0x31, 0x43, 0xb9, 0x0f, 0xe3, 0x86, 0xa7, 0x04, 0x33, 0x35, 0x9d, 0x4b, 0xc5, 0xaa, 0x6c, 0xa3, + 0xd1, 0x94, 0xcc, 0xc0, 0x74, 0xd8, 0xb7, 0x4c, 0xc8, 0x3f, 0x08, 0x2e, 0x8b, 0x5c, 0x1d, 0x91, + 0x3a, 0x7e, 0xab, 0x32, 0x32, 0x0b, 0xd7, 0x23, 0xc6, 0x65, 0x4a, 0xbe, 0x47, 0x70, 0xb5, 0xcc, + 0xcc, 0x3d, 0x9b, 0xfe, 0x07, 0x7b, 0x2a, 0xaa, 0x51, 0x03, 0xb5, 0x53, 0x89, 0x94, 0xf9, 0x33, + 0xf2, 0x0d, 0x78, 0x0b, 0x6c, 0x1d, 0x36, 0xea, 0x7b, 0x4e, 0xcd, 0xe0, 0xaf, 0xf2, 0x05, 0x1f, + 0xc2, 0x25, 0x6c, 0x73, 0x6a, 0x61, 0xa6, 0x0e, 0x8b, 0xad, 0xbc, 0x3c, 0x48, 0x6f, 0x78, 0x37, + 0x07, 0x73, 0xa3, 0xda, 0x17, 0x60, 0xbe, 0xa7, 0x3c, 0x69, 0xe0, 0x04, 0x41, 0xa6, 0xcc, 0xcc, + 0x27, 0x58, 0x54, 0x24, 0xbb, 0xb8, 0xc2, 0x7b, 0x0c, 0x13, 0x6e, 0x19, 0x55, 0x0e, 0x85, 0x9e, + 0x44, 0xa7, 0x97, 0x27, 0xdd, 0xf7, 0x9b, 0xa1, 0xf2, 0x4d, 0x87, 0xe7, 0xeb, 0x30, 0x15, 0x72, + 0x24, 0x9d, 0xfe, 0x81, 0xc4, 0xee, 0xdb, 0xa5, 0xc4, 0x21, 0x4c, 0x14, 0xdb, 0xf6, 0x81, 0x61, + 0x9b, 0xf8, 0xff, 0x60, 0x79, 0x0f, 0xde, 0xe9, 0x65, 0x2d, 0xf0, 0xae, 0xdc, 0x80, 0xf1, 0xaa, + 0x78, 0xe3, 0x9e, 0xed, 0xc2, 0xa5, 0x3e, 0xe6, 0xbd, 0xd8, 0xa9, 0x29, 0x2a, 0x5c, 0x32, 0x1c, + 0xa7, 0x61, 0xe1, 0x9a, 0x70, 0x34, 0xa6, 0x07, 0x8f, 0x79, 0x2a, 0x32, 0xb6, 0xe9, 0x08, 0x81, + 0xaf, 0x95, 0xb1, 0x88, 0x80, 0xe1, 0xa8, 0x80, 0xa8, 0x95, 0x0d, 0x61, 0xa5, 0x2b, 0xa6, 0xb4, + 0x12, 0x52, 0x8b, 0xa2, 0x6a, 0xbf, 0x14, 0xdf, 0x7d, 0xdb, 0xcd, 0x63, 0xe3, 0x82, 0xc4, 0xce, + 0xc3, 0x8d, 0x1e, 0x21, 0x65, 0xc9, 0xfd, 0x3a, 0x2c, 0xee, 0xc0, 0x6d, 0x8a, 0xc5, 0x96, 0x0b, + 0x5d, 0x96, 0xaf, 0xa0, 0x6a, 0xa5, 0xd7, 0x3d, 0xed, 0xa9, 0xeb, 0xbc, 0x88, 0x95, 0x25, 0x98, + 0x34, 0x18, 0xc3, 0xbc, 0x0d, 0xa6, 0x04, 0x38, 0x21, 0xde, 0x06, 0xd4, 0x06, 0x40, 0xd3, 0xb0, + 0x6c, 0x6e, 0x58, 0xae, 0x8e, 0xf4, 0x00, 0x1d, 0x21, 0x56, 0xf9, 0x02, 0xa6, 0x44, 0x25, 0x47, + 0x1a, 0x26, 0xa6, 0x8e, 0x88, 0x82, 0x7e, 0x2f, 0xae, 0xa0, 0x37, 0xc3, 0x33, 0xfc, 0xba, 0x56, + 0x68, 0xe7, 0x40, 0x47, 0x79, 0xe7, 0x20, 0xdb, 0x3b, 0x8d, 0xe1, 0x96, 0xe2, 0xa6, 0x7b, 0x48, + 0xfb, 0x87, 0x5b, 0x18, 0xe9, 0x5c, 0xfb, 0x0d, 0xa7, 0xbe, 0x4f, 0x6a, 0x52, 0x6f, 0x28, 0x35, + 0x6b, 0x50, 0x4a, 0xe8, 0x5b, 0xe6, 0xea, 0x27, 0x04, 0x57, 0xe4, 0x9c, 0x5d, 0xd1, 0xc6, 0x8a, + 0xcb, 0xdc, 0x83, 0x79, 0x6b, 0x60, 0x5a, 0xda, 0xa8, 0xf2, 0x01, 0x8c, 0x7a, 0x8d, 0xb0, 0x7f, + 0x18, 0x66, 0xfb, 0x19, 0xf4, 0xe2, 0xf8, 0xae, 0xfc, 0x39, 0x0f, 0x26, 0x5d, 0x27, 0xed, 0xd5, + 0xf2, 0x73, 0x30, 0xdb, 0x21, 0x2c, 0x10, 0xbd, 0xfe, 0x5d, 0x06, 0x52, 0x65, 0x66, 0x2a, 0x18, + 0x32, 0xe1, 0x2e, 0xfb, 0xdd, 0x7e, 0xf1, 0xa2, 0xad, 0xa7, 0x56, 0x4c, 0xc6, 0xc9, 0x53, 0xa6, + 0x02, 0xe3, 0xed, 0xf6, 0x74, 0x29, 0x66, 0xb2, 0xa4, 0xb4, 0x5b, 0x49, 0x28, 0x19, 0x60, 0x1f, + 0x20, 0xd4, 0xee, 0x2d, 0xc7, 0xca, 0x0b, 0x30, 0x6d, 0x35, 0x11, 0x26, 0x63, 0xd4, 0xe1, 0x72, + 0xb4, 0x7f, 0x2a, 0xc4, 0xcc, 0x8f, 0x90, 0xda, 0xed, 0xa4, 0xa4, 0x0c, 0xf6, 0x0c, 0x94, 0x1e, + 0x5d, 0xd0, 0xea, 0xc0, 0xbc, 0x87, 0x71, 0xed, 0xde, 0xb9, 0x70, 0x19, 0xfb, 0x73, 0x18, 0x93, + 0x0d, 0xcc, 0x62, 0xcc, 0x12, 0x01, 0xa4, 0xbd, 0x9f, 0x00, 0x92, 0xab, 0x7f, 0x05, 0xd7, 0xba, + 0x9b, 0x86, 0xb8, 0xaf, 0xdd, 0x45, 0x6b, 0x77, 0xcf, 0x43, 0x87, 0x03, 0x77, 0xdf, 0xbd, 0x71, + 0x81, 0xbb, 0xe8, 0xd8, 0xc0, 0xfd, 0xef, 0xd8, 0xaf, 0x61, 0xaa, 0xd7, 0x9d, 0x15, 0xb7, 0x89, + 0x7a, 0xf0, 0xda, 0xfd, 0xf3, 0xf1, 0x32, 0xfc, 0x31, 0x82, 0xa5, 0x44, 0x27, 0xf9, 0x47, 0x71, + 0x55, 0x9a, 0x60, 0x01, 0xed, 0xd1, 0x6b, 0x2e, 0x20, 0x25, 0x73, 0xb8, 0xda, 0xd5, 0x78, 0xc4, + 0x15, 0x59, 0x27, 0xac, 0xdd, 0x39, 0x07, 0x2c, 0xa3, 0x1e, 0xc0, 0x44, 0xe4, 0x14, 0xbf, 0x39, + 0xd0, 0x8e, 0x07, 0x6a, 0xa5, 0x84, 0x60, 0x10, 0x49, 0x1b, 0xf9, 0xe6, 0xe5, 0xf1, 0x0a, 0xda, + 0xaa, 0x3f, 0x3f, 0xcd, 0xa2, 0x93, 0xd3, 0x2c, 0xfa, 0xf3, 0x34, 0x8b, 0x7e, 0x38, 0xcb, 0x0e, + 0x9d, 0x9c, 0x65, 0x87, 0x7e, 0x3f, 0xcb, 0x0e, 0xc1, 0x9c, 0x45, 0xfa, 0xac, 0xb9, 0x8b, 0x3e, + 0xbb, 0x6b, 0x5a, 0xfc, 0xe0, 0x70, 0xbf, 0x58, 0x25, 0xcd, 0x52, 0x1b, 0x5a, 0xb5, 0x48, 0xe8, + 0xa9, 0xf4, 0xb4, 0xfd, 0xf7, 0x0a, 0x6f, 0x39, 0x98, 0xed, 0x8f, 0x8a, 0xff, 0x56, 0xee, 0xfc, + 0x1b, 0x00, 0x00, 0xff, 0xff, 0x61, 0xad, 0x4a, 0x71, 0x58, 0x12, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1373,6 +1470,9 @@ type MsgClient interface { // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry // class. Only the current maintainer may update the rules. UpdateRegistryClassRoleAuthorization(ctx context.Context, in *MsgUpdateRegistryClassRoleAuthorization, opts ...grpc.CallOption) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) + // CancelRoleChange cancels an open pending role change. + // Only the original proposer may cancel. + CancelRoleChange(ctx context.Context, in *MsgCancelRoleChange, opts ...grpc.CallOption) (*MsgCancelRoleChangeResponse, error) // UpdateParams is a governance proposal endpoint for updating the registry module's params, // including the default role authorization policies. The authority must be the governance module // account address. @@ -1477,6 +1577,15 @@ func (c *msgClient) UpdateRegistryClassRoleAuthorization(ctx context.Context, in return out, nil } +func (c *msgClient) CancelRoleChange(ctx context.Context, in *MsgCancelRoleChange, opts ...grpc.CallOption) (*MsgCancelRoleChangeResponse, error) { + out := new(MsgCancelRoleChangeResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/CancelRoleChange", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/UpdateParams", in, out, opts...) @@ -1524,6 +1633,9 @@ type MsgServer interface { // UpdateRegistryClassRoleAuthorization replaces the authorization rules for an existing registry // class. Only the current maintainer may update the rules. UpdateRegistryClassRoleAuthorization(context.Context, *MsgUpdateRegistryClassRoleAuthorization) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) + // CancelRoleChange cancels an open pending role change. + // Only the original proposer may cancel. + CancelRoleChange(context.Context, *MsgCancelRoleChange) (*MsgCancelRoleChangeResponse, error) // UpdateParams is a governance proposal endpoint for updating the registry module's params, // including the default role authorization policies. The authority must be the governance module // account address. @@ -1564,6 +1676,9 @@ func (*UnimplementedMsgServer) CreateRegistryClass(ctx context.Context, req *Msg func (*UnimplementedMsgServer) UpdateRegistryClassRoleAuthorization(ctx context.Context, req *MsgUpdateRegistryClassRoleAuthorization) (*MsgUpdateRegistryClassRoleAuthorizationResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateRegistryClassRoleAuthorization not implemented") } +func (*UnimplementedMsgServer) CancelRoleChange(ctx context.Context, req *MsgCancelRoleChange) (*MsgCancelRoleChangeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CancelRoleChange not implemented") +} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -1752,6 +1867,24 @@ func _Msg_UpdateRegistryClassRoleAuthorization_Handler(srv interface{}, ctx cont return interceptor(ctx, in, info, handler) } +func _Msg_CancelRoleChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgCancelRoleChange) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).CancelRoleChange(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/CancelRoleChange", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).CancelRoleChange(ctx, req.(*MsgCancelRoleChange)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -1815,6 +1948,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "UpdateRegistryClassRoleAuthorization", Handler: _Msg_UpdateRegistryClassRoleAuthorization_Handler, }, + { + MethodName: "CancelRoleChange", + Handler: _Msg_CancelRoleChange_Handler, + }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -2445,6 +2582,66 @@ func (m *MsgApproveRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, e return len(dAtA) - i, nil } +func (m *MsgCancelRoleChange) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCancelRoleChange) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCancelRoleChange) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.ChangeId) > 0 { + i -= len(m.ChangeId) + copy(dAtA[i:], m.ChangeId) + i = encodeVarintTx(dAtA, i, uint64(len(m.ChangeId))) + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgCancelRoleChangeResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgCancelRoleChangeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgCancelRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgCreateRegistryClass) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -2941,6 +3138,32 @@ func (m *MsgApproveRoleChangeResponse) Size() (n int) { return n } +func (m *MsgCancelRoleChange) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.ChangeId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgCancelRoleChangeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgCreateRegistryClass) Size() (n int) { if m == nil { return 0 @@ -4689,6 +4912,170 @@ func (m *MsgApproveRoleChangeResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgCancelRoleChange) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCancelRoleChange: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCancelRoleChange: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ChangeId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ChangeId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgCancelRoleChangeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgCancelRoleChangeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgCancelRoleChangeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgCreateRegistryClass) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 From 84307502e4b9d82db2ed542d03d1abd2e7ae44b9 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Tue, 21 Jul 2026 16:58:12 -0600 Subject: [PATCH 34/36] fix(registry): address Cursor Bugbot review findings BulkUpdate wrong address set: ValidateRoleChangeAuthorization now receives only the newly added addresses via additions(), matching the semantics of GrantRole and SetRoles. Passing the full desired list was incorrectly treating unchanged holders as incoming assignees under ASSIGNMENT_NEW* policies. Unregister deadlocks split owner/controller: The previous fix required the signer to be both the NFT owner and the controller, making unregistration impossible when the two are different accounts. Now the controller signs when one is set (NFT ownership not required), and the NFT owner signs only when no controller is set. Expiry bypass via propose: ProposeRoleChange now checks expiry when reusing an existing pending record (co-approval path), closing the bypass where MsgProposeRoleChange could apply a change after its expiry. Expired pending blocks proposals: ProposeRoleChange now cleans up expired pending records eagerly instead of treating them as live blockers, so a lapsed change no longer prevents new proposals indefinitely. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/keeper_test.go | 102 ++++++++++++++++++++++++++----- x/registry/keeper/msg_server.go | 33 ++++++---- x/registry/keeper/pending.go | 23 ++++++- 3 files changed, 131 insertions(+), 27 deletions(-) diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 79f4700ed4..7a32ea357a 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -695,8 +695,10 @@ func (s *KeeperTestSuite) GenesisTest() { s.Require().Equal(genesis1, genesis2) } -// TestUnregisterNFT_RequiresControllerSignature verifies that an NFT owner cannot unilaterally -// unregister an NFT while a CONTROLLER is set, preventing policy bypass via unregister+re-register. +// TestUnregisterNFT_RequiresControllerSignature verifies that when a CONTROLLER is set, the +// controller (not the NFT owner) must sign. When no controller is set, the NFT owner signs. +// This prevents an owner from bypassing policies via unregister+re-register while ensuring +// the controller can always unregister even if they don't own the NFT. func (s *KeeperTestSuite) TestUnregisterNFT_RequiresControllerSignature() { key := &types.RegistryKey{ AssetClassId: s.validNFTClass.Id, @@ -705,29 +707,28 @@ func (s *KeeperTestSuite) TestUnregisterNFT_RequiresControllerSignature() { require := s.Require() msgServer := keeper.NewMsgServer(s.app.RegistryKeeper) - // Register with user1 as CONTROLLER (user1 also owns the NFT via Mint in SetupTest). - require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ - {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, - }, "")) - - // user1 owns the NFT but is also the CONTROLLER, so unregistration is allowed. + // No controller set: NFT owner (user1) can unregister. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{}, "")) _, err := msgServer.UnregisterNFT(s.ctx, &types.MsgUnregisterNFT{Signer: s.user1, Key: key}) - require.NoError(err, "controller who also owns the NFT must be able to unregister") + require.NoError(err, "NFT owner must be able to unregister when no controller is set") - // Re-register with user2 as CONTROLLER. user1 still owns the NFT. + // Register with user2 as CONTROLLER. user1 still owns the NFT. require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, }, "")) - // user1 (NFT owner, but NOT the controller) tries to unregister — must be rejected. + // user1 (NFT owner, not controller) is rejected. _, err = msgServer.UnregisterNFT(s.ctx, &types.MsgUnregisterNFT{Signer: s.user1, Key: key}) - require.Error(err, "NFT owner should not be able to unregister while a different controller is set") + require.Error(err, "NFT owner without controller role must be rejected") require.Contains(err.Error(), "unauthorized") - // Entry must still exist. + // user2 (controller, not NFT owner) can unregister. + _, err = msgServer.UnregisterNFT(s.ctx, &types.MsgUnregisterNFT{Signer: s.user2, Key: key}) + require.NoError(err, "controller must be able to unregister even without owning the NFT") + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) require.NoError(err) - require.NotNil(entry, "registry entry must remain after rejected unregistration") + require.Nil(entry, "registry entry must be gone after controller unregistration") } // TestRegistryBulkUpdate_EnforcesRolePolicies verifies that RegistryBulkUpdate validates role @@ -936,6 +937,79 @@ func (s *KeeperTestSuite) TestPendingRoleChange_Expiry() { change, err := s.app.RegistryKeeper.GetPendingRoleChange(s.ctx, changeID) require.NoError(err) require.Nil(change, "expired change must be removed on failed approval") + + // After expiry cleans up via ApproveRoleChange, a new proposal must succeed. + _, _, err = s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err, "new proposal must be accepted after expired record is cleaned up") +} + +// TestPendingRoleChange_ExpiredBlocksProposal verifies that an expired pending change is cleaned +// up eagerly when a new proposal is made, instead of blocking it indefinitely. +func (s *KeeperTestSuite) TestPendingRoleChange_ExpiredBlocksProposal() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + PendingChangeExpiry: time.Hour, + })) + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + // First proposal leaves a pending change that will expire. + _, _, err := s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }) + require.NoError(err) + + // Advance past expiry. + s.ctx = s.ctx.WithBlockTime(s.ctx.BlockTime().Add(2 * time.Hour)) + + // A new proposal with different role updates must succeed: the expired record is cleaned up + // eagerly rather than blocking the new proposal. + _, _, err = s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }) + require.NoError(err, "proposal must succeed after expired pending change is cleaned up") +} + +// TestPendingRoleChange_ExpiryBypassViaPropose verifies that MsgProposeRoleChange also enforces +// expiry when reusing an existing pending record, preventing the expiry from being bypassed. +func (s *KeeperTestSuite) TestPendingRoleChange_ExpiryBypassViaPropose() { + key := &types.RegistryKey{ + AssetClassId: s.validNFTClass.Id, + NftId: s.validNFT.Id, + } + require := s.Require() + + require.NoError(s.app.RegistryKeeper.SetParams(s.ctx, types.Params{ + RoleAuthorizations: types.ControllerRoleAuthorizations(), + PendingChangeExpiry: time.Hour, + })) + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + // Propose a change; user2 has not yet co-signed. + roleUpdates := []types.RoleUpdate{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + } + _, _, err := s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user1, key, roleUpdates) + require.NoError(err) + + // Advance past expiry. + s.ctx = s.ctx.WithBlockTime(s.ctx.BlockTime().Add(2 * time.Hour)) + + // Attempting to co-approve via ProposeRoleChange (same change ID) must also be rejected. + _, _, err = s.app.RegistryKeeper.ProposeRoleChange(s.ctx, s.user2, key, roleUpdates) + require.Error(err, "propose co-approval on expired change must fail") + require.Contains(err.Error(), "pending role change not found") } func (s *KeeperTestSuite) TestRegisterNFTMsgServer() { diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index 3abfc56232..fd40182319 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -273,22 +273,29 @@ func (k msgServer) UpdateParams(ctx context.Context, msg *types.MsgUpdateParams) // UnregisterNFT unregisters an NFT from the registry. // This removes the entire registry entry and associated data for the specified key. func (k msgServer) UnregisterNFT(ctx context.Context, msg *types.MsgUnregisterNFT) (*types.MsgUnregisterNFTResponse, error) { - // Validate that the signer owns the NFT - if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { - return nil, err - } - - // If a CONTROLLER is set on the entry, the signer must also be the CONTROLLER. Without this - // check an NFT owner could bypass multi-party role policies by unregistering and re-registering - // with new roles. entry, err := k.GetRegistry(ctx, msg.Key) if err != nil { return nil, fmt.Errorf("could not get registry entry: %w", err) } + + // If a CONTROLLER is set, the CONTROLLER must sign. This prevents an NFT owner from bypassing + // multi-party role policies by unregistering and re-registering with new roles. The controller + // does not need to own the NFT — the controller role is authoritative for this action when set. + // If no controller is set, fall back to NFT-ownership authorization. if entry != nil { controllers := entry.GetRoleAddrs(types.RegistryRole_REGISTRY_ROLE_CONTROLLER) - if len(controllers) > 0 && !slices.Contains(controllers, msg.Signer) { - return nil, types.NewErrCodeUnauthorized("signer is not the controller") + if len(controllers) > 0 { + if !slices.Contains(controllers, msg.Signer) { + return nil, types.NewErrCodeUnauthorized("signer is not the controller") + } + } else { + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { + return nil, err + } + } + } else { + if err := k.ValidateNFTOwner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { + return nil, err } } @@ -345,7 +352,11 @@ func (k msgServer) RegistryBulkUpdate(ctx context.Context, msg *types.MsgRegistr // Validate roles being set or changed to a new desired state. for _, roleEntry := range entry.Roles { if roleAuth, ok := roleAuths[roleEntry.Role]; ok { - if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, orig, roleEntry.Addresses, []string{msg.Signer}); err != nil { + // Pass only newly added addresses (not the full list) to match the semantics of + // GrantRole / SetRoles, where ASSIGNMENT_NEW* policies are evaluated against + // addresses that are incoming — not those already holding the role. + newAddrs := additions(orig.GetRoleAddrs(roleEntry.Role), roleEntry.Addresses) + if err := k.Keeper.ValidateRoleChangeAuthorization(ctx, roleAuth, orig, newAddrs, []string{msg.Signer}); err != nil { return nil, fmt.Errorf("[%d] unauthorized update for role %s: %w", i, roleEntry.Role.ShortString(), err) } } diff --git a/x/registry/keeper/pending.go b/x/registry/keeper/pending.go index 98473d5bc1..6da9e88078 100644 --- a/x/registry/keeper/pending.go +++ b/x/registry/keeper/pending.go @@ -128,12 +128,21 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ if change == nil { // Reject if there's already a different pending change for this key. Limiting to one // pending change per NFT prevents conflicting proposals from accumulating; the existing - // change must be cancelled or applied first. + // change must be cancelled or applied first. Expired records are cleaned up eagerly here + // so they don't block new proposals indefinitely. existing, _, err := k.GetPendingRoleChanges(ctx, nil, key) if err != nil { return "", false, err } - if len(existing) > 0 { + blockTime := sdk.UnwrapSDKContext(ctx).BlockTime() + for _, ex := range existing { + if !ex.ExpiresAt.IsZero() && !blockTime.Before(ex.ExpiresAt) { + // Expired — remove it so it no longer blocks new proposals. + if rerr := k.RemovePendingRoleChange(ctx, ex.Id); rerr != nil { + return "", false, rerr + } + continue + } return "", false, types.NewErrCodeInvalidField("role_updates", "a pending role change already exists for this NFT; cancel or apply the existing change first") } @@ -162,6 +171,16 @@ func (k Keeper) ProposeRoleChange(ctx context.Context, proposer string, key *typ } change = newChange k.EmitEvent(ctx, types.NewEventRoleChangeProposed(change)) + } else { + // Reusing an existing pending record (same change ID = same role updates, acting as a + // co-approval). Check expiry here too so that MsgProposeRoleChange cannot be used to + // bypass the expiry enforcement that applies to MsgApproveRoleChange. + if !change.ExpiresAt.IsZero() && !sdk.UnwrapSDKContext(ctx).BlockTime().Before(change.ExpiresAt) { + if rerr := k.RemovePendingRoleChange(ctx, change.Id); rerr != nil { + return "", false, rerr + } + return "", false, types.NewErrCodePendingChangeNotFound(change.Id + " (expired)") + } } applied, err := k.recordApprovalAndMaybeApply(ctx, entry, change, proposer) From f3310ac9375fd7825a15dba439cfe24f321de0a9 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Wed, 22 Jul 2026 14:45:24 -0600 Subject: [PATCH 35/36] feat(registry): add MsgAssociateRegistryClass for upgrading legacy entries to use a registry class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new MsgAssociateRegistryClass message that allows an existing legacy registry entry (one created without a registry_class_id) to be associated with a registry class without modifying its roles. Authorization: either the CONTROLLER role address(es) OR the scope data-owner parties (if the NFT is a Provenance Metadata Scope) OR the NFT owner — no precedence, any one is sufficient. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../improvements/associate-registry-class.md | 4 + proto/provenance/registry/v1/tx.proto | 31 + x/registry/keeper/keeper_test.go | 207 ++++++ x/registry/keeper/msg_server.go | 33 + x/registry/keeper/nft.go | 22 +- x/registry/types/msgs.go | 18 + x/registry/types/tx.pb.go | 610 +++++++++++++++--- 7 files changed, 849 insertions(+), 76 deletions(-) create mode 100644 .changelog/unreleased/improvements/associate-registry-class.md diff --git a/.changelog/unreleased/improvements/associate-registry-class.md b/.changelog/unreleased/improvements/associate-registry-class.md new file mode 100644 index 0000000000..92df6bd4c2 --- /dev/null +++ b/.changelog/unreleased/improvements/associate-registry-class.md @@ -0,0 +1,4 @@ +* Add `MsgAssociateRegistryClass` to the registry module, allowing an existing legacy registry + entry to be upgraded to use a registry class without modifying roles. Authorization accepts any + of: CONTROLLER role address, scope data-owner party (for Provenance Metadata Scopes), or NFT + owner — neither takes precedence. diff --git a/proto/provenance/registry/v1/tx.proto b/proto/provenance/registry/v1/tx.proto index 596a9a169c..9a7d24d2c6 100644 --- a/proto/provenance/registry/v1/tx.proto +++ b/proto/provenance/registry/v1/tx.proto @@ -68,6 +68,11 @@ service Msg { // Only the original proposer may cancel. rpc CancelRoleChange(MsgCancelRoleChange) returns (MsgCancelRoleChangeResponse); + // AssociateRegistryClass associates or updates the registry class for an existing registry entry + // without modifying any roles. The signer must be the CONTROLLER if one is set, or the scope + // data owner if the NFT is a Provenance Metadata Scope, or the NFT owner otherwise. + rpc AssociateRegistryClass(MsgAssociateRegistryClass) returns (MsgAssociateRegistryClassResponse); + // UpdateParams is a governance proposal endpoint for updating the registry module's params, // including the default role authorization policies. The authority must be the governance module // account address. @@ -274,6 +279,32 @@ message MsgCancelRoleChange { // MsgCancelRoleChangeResponse defines the response for CancelRoleChange. message MsgCancelRoleChangeResponse {} +// MsgAssociateRegistryClass associates or updates the registry class for an existing registry +// entry without modifying any roles. This is the upgrade path from legacy (no-class) registration +// to class-governed authorization. +// +// Authorization: the signer must satisfy at least one of the following — neither takes precedence: +// - CONTROLLER: the signer is one of the entry's CONTROLLER role addresses. +// - Scope data owner: if the NFT is a Provenance Metadata Scope, the signer is one of the +// scope's data-owner parties. +// - NFT owner: for all other NFTs, the signer owns the NFT. +message MsgAssociateRegistryClass { + option (cosmos.msg.v1.signer) = "signer"; + + // signer is the address authorizing this association. + string signer = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + + // key identifies the existing registry entry to update. + RegistryKey key = 2; + + // registry_class_id is the ID of the registry class to associate. Must be non-empty; the + // referenced class must exist and its asset_class_id must match the entry's asset_class_id. + string registry_class_id = 3 [(gogoproto.jsontag) = "registryClassId,omitempty"]; +} + +// MsgAssociateRegistryClassResponse defines the response for AssociateRegistryClass. +message MsgAssociateRegistryClassResponse {} + // MsgCreateRegistryClass creates a new registry class defining asset class-level authorization // rules for role updates within an NFT collection. message MsgCreateRegistryClass { diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 7a32ea357a..676c2f6e21 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/stretchr/testify/suite" "cosmossdk.io/x/nft" @@ -17,6 +18,7 @@ import ( "github.com/cosmos/cosmos-sdk/types/query" "github.com/provenance-io/provenance/app" + metadatatypes "github.com/provenance-io/provenance/x/metadata/types" "github.com/provenance-io/provenance/testutil/assertions" "github.com/provenance-io/provenance/x/registry/keeper" "github.com/provenance-io/provenance/x/registry/types" @@ -1121,3 +1123,208 @@ func (s *KeeperTestSuite) TestRegisterNFTMsgServer() { }) } } + +// TestAssociateRegistryClass_ControllerCanAssociate verifies that a CONTROLLER signer can +// associate a registry class on an existing entry. +func (s *KeeperTestSuite) TestAssociateRegistryClass_ControllerCanAssociate() { + require := s.Require() + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + + // Create a class whose asset_class_id matches the entry. + class := types.RegistryClass{ + RegistryClassId: "test-class", + AssetClassId: s.validNFTClass.Id, + Maintainer: s.user1, + RoleAuthorizations: nil, + } + require.NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + + // Register with a controller but no class yet. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user1, + Key: key, + RegistryClassId: "test-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.NoError(err, "controller must be allowed to associate a registry class") + + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) + require.NoError(err) + require.NotNil(entry) + require.Equal("test-class", entry.RegistryClassId, "registry_class_id must be updated") +} + +// TestAssociateRegistryClass_NFTOwnerCanAssociateWhenControllerSet verifies that the NFT owner can +// associate a registry class even when a separate controller is set — neither takes precedence. +func (s *KeeperTestSuite) TestAssociateRegistryClass_NFTOwnerCanAssociateWhenControllerSet() { + require := s.Require() + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + + class := types.RegistryClass{ + RegistryClassId: "test-class", + AssetClassId: s.validNFTClass.Id, + Maintainer: s.user1, + } + require.NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + + // user2 is the controller; user1 owns the NFT. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user2}}, + }, "")) + + // user1 (NFT owner, not controller) must still be able to associate. + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user1, + Key: key, + RegistryClassId: "test-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.NoError(err, "NFT owner must be able to associate even when a controller is set") + + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) + require.NoError(err) + require.Equal("test-class", entry.RegistryClassId) +} + + +// controller nor the NFT owner is rejected even when a controller is set on the entry. +func (s *KeeperTestSuite) TestAssociateRegistryClass_UnauthorizedSignerRejected() { + require := s.Require() + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + + class := types.RegistryClass{ + RegistryClassId: "test-class", + AssetClassId: s.validNFTClass.Id, + Maintainer: s.user1, + } + require.NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + + // user1 is the controller AND NFT owner; user2 is neither. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{ + {Role: types.RegistryRole_REGISTRY_ROLE_CONTROLLER, Addresses: []string{s.user1}}, + }, "")) + + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user2, + Key: key, + RegistryClassId: "test-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.Error(err, "signer who is neither controller nor NFT owner must be rejected") +} + +// TestAssociateRegistryClass_NFTOwnerCanAssociateWithoutController verifies that the NFT owner can +// associate a class when no controller is set on the entry. +func (s *KeeperTestSuite) TestAssociateRegistryClass_NFTOwnerCanAssociateWithoutController() { + require := s.Require() + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + + class := types.RegistryClass{ + RegistryClassId: "test-class", + AssetClassId: s.validNFTClass.Id, + Maintainer: s.user1, + } + require.NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + + // No controller set — entry created by user1 who also owns the NFT. + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{}, "")) + + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user1, + Key: key, + RegistryClassId: "test-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.NoError(err, "NFT owner must be allowed when no controller is set") + + entry, err := s.app.RegistryKeeper.GetRegistry(s.ctx, key) + require.NoError(err) + require.Equal("test-class", entry.RegistryClassId) +} + +// TestAssociateRegistryClass_ScopeDataOwnerCanAssociate verifies that a scope data-owner party can +// associate a registry class when the NFT is a Provenance Metadata Scope. +func (s *KeeperTestSuite) TestAssociateRegistryClass_ScopeDataOwnerCanAssociate() { + require := s.Require() + + // Create a real Provenance Metadata Scope. + scopeID := metadatatypes.ScopeMetadataAddress(uuid.New()) + scope := metadatatypes.NewScope( + scopeID, + metadatatypes.MetadataAddress{}, // no spec required for this test + []metadatatypes.Party{ + {Address: s.user1, Role: metadatatypes.PartyType_PARTY_TYPE_OWNER}, + }, + nil, // no data access + "", // no value owner + false, // no party rollup + ) + require.NoError(s.app.MetadataKeeper.SetScope(s.ctx, *scope)) + + // The "asset class" for a Scope is a scope-spec address; for this test we reuse the existing + // nft class and rely on the fact that the registry module validates the NFT exists via scope + // lookup, not class lookup for scope-type NFTs. Use the scope bech32 as the nftID. + scopeBech32 := scopeID.String() + + // We need an asset_class_id for the RegistryKey — use the NFT class id as a stand-in. + assetClassID := s.validNFTClass.Id + key := &types.RegistryKey{AssetClassId: assetClassID, NftId: scopeBech32} + + class := types.RegistryClass{ + RegistryClassId: "scope-class", + AssetClassId: assetClassID, + Maintainer: s.user1, + } + require.NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + + // Register without controller — no scope spec check needed for this path. + require.NoError(s.app.RegistryKeeper.SetRegistry(s.ctx, types.RegistryEntry{ + Key: key, + Roles: []types.RolesEntry{}, + RegistryClassId: "", + })) + + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user1, // user1 is a scope Owner party (data owner) + Key: key, + RegistryClassId: "scope-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.NoError(err, "scope data owner must be allowed to associate a registry class") +} + +// TestAssociateRegistryClass_EmptyClassIdRejected verifies that an empty registry_class_id is +// rejected at ValidateBasic. +func (s *KeeperTestSuite) TestAssociateRegistryClass_EmptyClassIdRejected() { + require := s.Require() + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: s.validNFT.Id} + require.NoError(s.app.RegistryKeeper.CreateRegistry(s.ctx, key, []types.RolesEntry{}, "")) + + msg := types.MsgAssociateRegistryClass{ + Signer: s.user1, + Key: key, + RegistryClassId: "", + } + err := msg.ValidateBasic() + require.Error(err, "empty registry_class_id must be rejected by ValidateBasic") + require.Contains(err.Error(), "registry_class_id") +} + +// TestAssociateRegistryClass_EntryNotFound verifies that the handler returns an error when there +// is no existing registry entry for the given key. +func (s *KeeperTestSuite) TestAssociateRegistryClass_EntryNotFound() { + require := s.Require() + key := &types.RegistryKey{AssetClassId: s.validNFTClass.Id, NftId: "non-existent-nft"} + + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user1, + Key: key, + RegistryClassId: "some-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.Error(err, "must return an error when the registry entry does not exist") +} diff --git a/x/registry/keeper/msg_server.go b/x/registry/keeper/msg_server.go index fd40182319..776a9a214e 100644 --- a/x/registry/keeper/msg_server.go +++ b/x/registry/keeper/msg_server.go @@ -232,6 +232,39 @@ func (k msgServer) CancelRoleChange(ctx context.Context, msg *types.MsgCancelRol return &types.MsgCancelRoleChangeResponse{}, nil } +// AssociateRegistryClass associates or updates the registry class on an existing entry. +// The entry must already exist; use RegisterNFT with a registry_class_id to set it on creation. +// Auth: CONTROLLER or scope data owner (if NFT is a Metadata Scope) or NFT owner — either is sufficient. +func (k msgServer) AssociateRegistryClass(ctx context.Context, msg *types.MsgAssociateRegistryClass) (*types.MsgAssociateRegistryClassResponse, error) { + entry, err := k.GetRegistry(ctx, msg.Key) + if err != nil { + return nil, fmt.Errorf("could not get registry entry: %w", err) + } + if entry == nil { + return nil, types.NewErrCodeRegistryNotFound(msg.Key.NftId) + } + + // Either a CONTROLLER or the scope data owner / NFT owner may associate a registry class. + // Neither takes precedence — the first matching condition is sufficient. + isController := slices.Contains(entry.GetRoleAddrs(types.RegistryRole_REGISTRY_ROLE_CONTROLLER), msg.Signer) + if !isController { + if err := k.validateAssociateRegistryClassSigner(ctx, &msg.Key.AssetClassId, &msg.Key.NftId, msg.Signer); err != nil { + return nil, err + } + } + + if err := k.validateRegistryClassForEntry(ctx, msg.RegistryClassId, entry.Key.AssetClassId); err != nil { + return nil, err + } + + entry.RegistryClassId = msg.RegistryClassId + if err := k.SetRegistry(ctx, *entry); err != nil { + return nil, err + } + + return &types.MsgAssociateRegistryClassResponse{}, nil +} + // CreateRegistryClass creates a new registry class defining asset class-level authorization rules. func (k msgServer) CreateRegistryClass(ctx context.Context, msg *types.MsgCreateRegistryClass) (*types.MsgCreateRegistryClassResponse, error) { class := types.RegistryClass{ diff --git a/x/registry/keeper/nft.go b/x/registry/keeper/nft.go index 78c10abf16..c31e1e274d 100644 --- a/x/registry/keeper/nft.go +++ b/x/registry/keeper/nft.go @@ -59,7 +59,27 @@ func (k Keeper) GetNFTOwner(ctx context.Context, assetClassID, nftID *string) sd return k.NFTKeeper.GetOwner(ctx, *assetClassID, *nftID) } -// ValidateNFTOwner returns nil if the described NFT is owned by the expOwner. +// validateAssociateRegistryClassSigner checks whether signer is authorized at the NFT-ownership +// tier for AssociateRegistryClass. For Metadata Scopes the signer must be one of the scope's +// data-owner parties; for all other NFTs the signer must be the value/NFT owner. +func (k Keeper) validateAssociateRegistryClassSigner(ctx context.Context, assetClassID, nftID *string, signer string) error { + metadataAddress, isMetadataScope := types.MetadataScopeID(*nftID) + if isMetadataScope { + sdkCtx := sdk.UnwrapSDKContext(ctx) + scope, found := k.MetadataKeeper.GetScope(sdkCtx, metadataAddress) + if !found { + return types.NewErrCodeNFTNotFound(*nftID) + } + for _, party := range scope.Owners { + if party.Address == signer { + return nil + } + } + return types.NewErrCodeUnauthorized("signer is not a data owner of the scope") + } + return k.ValidateNFTOwner(ctx, assetClassID, nftID, signer) +} + // Returns an error if owned by someone else, or if the NFT doesn't exist. func (k Keeper) ValidateNFTOwner(ctx context.Context, assetClassID, nftID *string, expOwner string) error { nftOwner := k.GetNFTOwner(ctx, assetClassID, nftID) diff --git a/x/registry/types/msgs.go b/x/registry/types/msgs.go index a49b380e2e..62feb80b2f 100644 --- a/x/registry/types/msgs.go +++ b/x/registry/types/msgs.go @@ -191,6 +191,24 @@ func (m MsgCancelRoleChange) ValidateBasic() error { return errors.Join(errs...) } +// ValidateBasic validates the MsgAssociateRegistryClass message. +func (m MsgAssociateRegistryClass) ValidateBasic() error { + var errs []error + if _, err := sdk.AccAddressFromBech32(m.Signer); err != nil { + errs = append(errs, NewErrCodeInvalidField("signer", "%s", err)) + } + + if m.Key == nil { + errs = append(errs, NewErrCodeInvalidField("key", "key is required")) + } + + if len(m.RegistryClassId) == 0 { + errs = append(errs, NewErrCodeInvalidField("registry_class_id", "registry_class_id is required")) + } + + return errors.Join(errs...) +} + // ValidateBasic validates the MsgCreateRegistryClass message func (m MsgCreateRegistryClass) ValidateBasic() error { var errs []error diff --git a/x/registry/types/tx.pb.go b/x/registry/types/tx.pb.go index e80580bbb6..e6241fd210 100644 --- a/x/registry/types/tx.pb.go +++ b/x/registry/types/tx.pb.go @@ -998,6 +998,117 @@ func (m *MsgCancelRoleChangeResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgCancelRoleChangeResponse proto.InternalMessageInfo +// MsgAssociateRegistryClass associates or updates the registry class for an existing registry +// entry without modifying any roles. This is the upgrade path from legacy (no-class) registration +// to class-governed authorization. +// +// Authorization priority: +// 1. CONTROLLER: if the entry has a CONTROLLER role set, the signer must be one of those +// controllers. +// 2. Scope data owner: if the NFT is a Provenance Metadata Scope, the signer must be one of +// the scope's data-owner parties. +// 3. NFT owner: for all other NFTs with no controller set, the signer must own the NFT. +type MsgAssociateRegistryClass struct { + // signer is the address authorizing this association. + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` + // key identifies the existing registry entry to update. + Key *RegistryKey `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + // registry_class_id is the ID of the registry class to associate. Pass an empty string to + // remove the current association and fall back to module-level authorization. + RegistryClassId string `protobuf:"bytes,3,opt,name=registry_class_id,json=registryClassId,proto3" json:"registryClassId,omitempty"` +} + +func (m *MsgAssociateRegistryClass) Reset() { *m = MsgAssociateRegistryClass{} } +func (m *MsgAssociateRegistryClass) String() string { return proto.CompactTextString(m) } +func (*MsgAssociateRegistryClass) ProtoMessage() {} +func (*MsgAssociateRegistryClass) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{18} +} +func (m *MsgAssociateRegistryClass) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAssociateRegistryClass) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAssociateRegistryClass.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAssociateRegistryClass) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAssociateRegistryClass.Merge(m, src) +} +func (m *MsgAssociateRegistryClass) XXX_Size() int { + return m.Size() +} +func (m *MsgAssociateRegistryClass) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAssociateRegistryClass.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAssociateRegistryClass proto.InternalMessageInfo + +func (m *MsgAssociateRegistryClass) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +func (m *MsgAssociateRegistryClass) GetKey() *RegistryKey { + if m != nil { + return m.Key + } + return nil +} + +func (m *MsgAssociateRegistryClass) GetRegistryClassId() string { + if m != nil { + return m.RegistryClassId + } + return "" +} + +// MsgAssociateRegistryClassResponse defines the response for AssociateRegistryClass. +type MsgAssociateRegistryClassResponse struct { +} + +func (m *MsgAssociateRegistryClassResponse) Reset() { *m = MsgAssociateRegistryClassResponse{} } +func (m *MsgAssociateRegistryClassResponse) String() string { return proto.CompactTextString(m) } +func (*MsgAssociateRegistryClassResponse) ProtoMessage() {} +func (*MsgAssociateRegistryClassResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_afab3f18b6d8353c, []int{19} +} +func (m *MsgAssociateRegistryClassResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgAssociateRegistryClassResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgAssociateRegistryClassResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgAssociateRegistryClassResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgAssociateRegistryClassResponse.Merge(m, src) +} +func (m *MsgAssociateRegistryClassResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgAssociateRegistryClassResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgAssociateRegistryClassResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgAssociateRegistryClassResponse proto.InternalMessageInfo + // MsgCreateRegistryClass creates a new registry class defining asset class-level authorization // rules for role updates within an NFT collection. type MsgCreateRegistryClass struct { @@ -1019,7 +1130,7 @@ func (m *MsgCreateRegistryClass) Reset() { *m = MsgCreateRegistryClass{} func (m *MsgCreateRegistryClass) String() string { return proto.CompactTextString(m) } func (*MsgCreateRegistryClass) ProtoMessage() {} func (*MsgCreateRegistryClass) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{18} + return fileDescriptor_afab3f18b6d8353c, []int{20} } func (m *MsgCreateRegistryClass) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1091,7 +1202,7 @@ func (m *MsgCreateRegistryClassResponse) Reset() { *m = MsgCreateRegistr func (m *MsgCreateRegistryClassResponse) String() string { return proto.CompactTextString(m) } func (*MsgCreateRegistryClassResponse) ProtoMessage() {} func (*MsgCreateRegistryClassResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{19} + return fileDescriptor_afab3f18b6d8353c, []int{21} } func (m *MsgCreateRegistryClassResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1137,7 +1248,7 @@ func (m *MsgUpdateRegistryClassRoleAuthorization) Reset() { func (m *MsgUpdateRegistryClassRoleAuthorization) String() string { return proto.CompactTextString(m) } func (*MsgUpdateRegistryClassRoleAuthorization) ProtoMessage() {} func (*MsgUpdateRegistryClassRoleAuthorization) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{20} + return fileDescriptor_afab3f18b6d8353c, []int{22} } func (m *MsgUpdateRegistryClassRoleAuthorization) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1200,7 +1311,7 @@ func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) String() string { } func (*MsgUpdateRegistryClassRoleAuthorizationResponse) ProtoMessage() {} func (*MsgUpdateRegistryClassRoleAuthorizationResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{21} + return fileDescriptor_afab3f18b6d8353c, []int{23} } func (m *MsgUpdateRegistryClassRoleAuthorizationResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1241,7 +1352,7 @@ func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParams) ProtoMessage() {} func (*MsgUpdateParams) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{22} + return fileDescriptor_afab3f18b6d8353c, []int{24} } func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1292,7 +1403,7 @@ func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } func (*MsgUpdateParamsResponse) ProtoMessage() {} func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_afab3f18b6d8353c, []int{23} + return fileDescriptor_afab3f18b6d8353c, []int{25} } func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -1340,6 +1451,8 @@ func init() { proto.RegisterType((*MsgApproveRoleChangeResponse)(nil), "provenance.registry.v1.MsgApproveRoleChangeResponse") proto.RegisterType((*MsgCancelRoleChange)(nil), "provenance.registry.v1.MsgCancelRoleChange") proto.RegisterType((*MsgCancelRoleChangeResponse)(nil), "provenance.registry.v1.MsgCancelRoleChangeResponse") + proto.RegisterType((*MsgAssociateRegistryClass)(nil), "provenance.registry.v1.MsgAssociateRegistryClass") + proto.RegisterType((*MsgAssociateRegistryClassResponse)(nil), "provenance.registry.v1.MsgAssociateRegistryClassResponse") proto.RegisterType((*MsgCreateRegistryClass)(nil), "provenance.registry.v1.MsgCreateRegistryClass") proto.RegisterType((*MsgCreateRegistryClassResponse)(nil), "provenance.registry.v1.MsgCreateRegistryClassResponse") proto.RegisterType((*MsgUpdateRegistryClassRoleAuthorization)(nil), "provenance.registry.v1.MsgUpdateRegistryClassRoleAuthorization") @@ -1351,75 +1464,78 @@ func init() { func init() { proto.RegisterFile("provenance/registry/v1/tx.proto", fileDescriptor_afab3f18b6d8353c) } var fileDescriptor_afab3f18b6d8353c = []byte{ - // 1085 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x98, 0xcf, 0x4f, 0x1b, 0x47, - 0x14, 0xc7, 0x19, 0x6c, 0x08, 0x3c, 0x13, 0x92, 0x2c, 0x04, 0x96, 0x4d, 0x31, 0x96, 0x81, 0xc6, - 0xa5, 0xc1, 0x0e, 0xe4, 0x87, 0x50, 0x54, 0xb5, 0x02, 0x94, 0x46, 0x28, 0x72, 0x85, 0x36, 0xe5, - 0x52, 0x55, 0x72, 0x17, 0x7b, 0xb4, 0xac, 0x6c, 0xef, 0x6c, 0x67, 0x06, 0x1a, 0x47, 0xaa, 0x54, - 0xb5, 0x97, 0x1e, 0xdb, 0x53, 0x4f, 0xfd, 0x1f, 0x38, 0xf4, 0x5f, 0xa8, 0x94, 0x23, 0xea, 0xa9, - 0xbd, 0x44, 0x15, 0x1c, 0x22, 0xf5, 0x0f, 0x68, 0xaf, 0xd5, 0xce, 0xee, 0x8e, 0x77, 0xfd, 0x63, - 0xbd, 0x24, 0x0a, 0x95, 0x9a, 0x9b, 0x77, 0xe7, 0x33, 0xf3, 0xbe, 0xdf, 0xb7, 0x6f, 0x66, 0x9e, - 0x0c, 0x0b, 0x0e, 0x25, 0x47, 0xd8, 0x36, 0xec, 0x2a, 0x2e, 0x51, 0x6c, 0x5a, 0x8c, 0xd3, 0x56, - 0xe9, 0x68, 0xad, 0xc4, 0x9f, 0x16, 0x1d, 0x4a, 0x38, 0x51, 0x66, 0xda, 0x40, 0x31, 0x00, 0x8a, - 0x47, 0x6b, 0xda, 0xb4, 0x49, 0x4c, 0x22, 0x90, 0x92, 0xfb, 0xcb, 0xa3, 0xb5, 0xb9, 0x2a, 0x61, - 0x4d, 0xc2, 0x2a, 0xde, 0x80, 0xf7, 0xe0, 0x0f, 0xcd, 0x7a, 0x4f, 0xa5, 0x26, 0x33, 0xdd, 0x00, - 0x4d, 0x66, 0xfa, 0x03, 0xcb, 0x7d, 0x24, 0xc8, 0x68, 0x1e, 0xb6, 0xd2, 0x07, 0x33, 0x0e, 0xf9, - 0x01, 0xa1, 0xd6, 0x33, 0x83, 0x5b, 0xc4, 0xf6, 0xd9, 0xc5, 0x3e, 0xac, 0x63, 0x50, 0xa3, 0xe9, - 0x0b, 0xca, 0xff, 0x38, 0x0c, 0x93, 0x65, 0x66, 0xea, 0x62, 0x1c, 0xd3, 0x4f, 0x3e, 0xfe, 0x54, - 0xb9, 0x0d, 0xa3, 0xcc, 0x32, 0x6d, 0x4c, 0x55, 0x94, 0x43, 0x85, 0xf1, 0x2d, 0xf5, 0xb7, 0x5f, - 0x56, 0xa7, 0x7d, 0x17, 0x9b, 0xb5, 0x1a, 0xc5, 0x8c, 0x3d, 0xe1, 0xd4, 0xb2, 0x4d, 0xdd, 0xe7, - 0x94, 0x7b, 0x90, 0xaa, 0xe3, 0x96, 0x3a, 0x9c, 0x43, 0x85, 0xcc, 0xfa, 0x62, 0xb1, 0x77, 0xb2, - 0x8a, 0xba, 0xff, 0xfb, 0x31, 0x6e, 0xe9, 0x2e, 0xaf, 0x7c, 0x08, 0x23, 0x94, 0x34, 0x30, 0x53, - 0x53, 0xb9, 0x54, 0x21, 0xb3, 0x9e, 0xef, 0x3b, 0xd1, 0x85, 0x1e, 0xda, 0x9c, 0xb6, 0xb6, 0xd2, - 0xcf, 0x5f, 0x2c, 0x0c, 0xe9, 0xde, 0x34, 0x65, 0x07, 0xae, 0x05, 0x58, 0xa5, 0xda, 0x30, 0x18, - 0xab, 0x58, 0x35, 0x35, 0x2d, 0x34, 0xcf, 0xff, 0xf5, 0x62, 0x61, 0x2e, 0x18, 0xdc, 0x76, 0xc7, - 0x76, 0x6a, 0xb7, 0x48, 0xd3, 0xe2, 0xb8, 0xe9, 0xf0, 0x96, 0x7e, 0xa5, 0x63, 0xe8, 0x41, 0xe6, - 0xdb, 0x97, 0xc7, 0x2b, 0xbe, 0x9d, 0xbc, 0x0a, 0x33, 0xd1, 0x94, 0xe8, 0x98, 0x39, 0xc4, 0x66, - 0x38, 0xff, 0x37, 0x82, 0x89, 0x32, 0x33, 0x1f, 0x51, 0xc3, 0xe6, 0xae, 0xaa, 0x8b, 0xcb, 0xd5, - 0x06, 0xa4, 0x5d, 0xd3, 0x6a, 0x2a, 0x87, 0x0a, 0x93, 0xeb, 0x4b, 0x83, 0xe6, 0xb9, 0xe2, 0x74, - 0x31, 0x43, 0xb9, 0x0f, 0xe3, 0x86, 0xa7, 0x04, 0x33, 0x35, 0x9d, 0x4b, 0xc5, 0xaa, 0x6c, 0xa3, - 0xd1, 0x94, 0xcc, 0xc0, 0x74, 0xd8, 0xb7, 0x4c, 0xc8, 0x3f, 0x08, 0x2e, 0x8b, 0x5c, 0x1d, 0x91, - 0x3a, 0x7e, 0xab, 0x32, 0x32, 0x0b, 0xd7, 0x23, 0xc6, 0x65, 0x4a, 0xbe, 0x47, 0x70, 0xb5, 0xcc, - 0xcc, 0x3d, 0x9b, 0xfe, 0x07, 0x7b, 0x2a, 0xaa, 0x51, 0x03, 0xb5, 0x53, 0x89, 0x94, 0xf9, 0x33, - 0xf2, 0x0d, 0x78, 0x0b, 0x6c, 0x1d, 0x36, 0xea, 0x7b, 0x4e, 0xcd, 0xe0, 0xaf, 0xf2, 0x05, 0x1f, - 0xc2, 0x25, 0x6c, 0x73, 0x6a, 0x61, 0xa6, 0x0e, 0x8b, 0xad, 0xbc, 0x3c, 0x48, 0x6f, 0x78, 0x37, - 0x07, 0x73, 0xa3, 0xda, 0x17, 0x60, 0xbe, 0xa7, 0x3c, 0x69, 0xe0, 0x04, 0x41, 0xa6, 0xcc, 0xcc, - 0x27, 0x58, 0x54, 0x24, 0xbb, 0xb8, 0xc2, 0x7b, 0x0c, 0x13, 0x6e, 0x19, 0x55, 0x0e, 0x85, 0x9e, - 0x44, 0xa7, 0x97, 0x27, 0xdd, 0xf7, 0x9b, 0xa1, 0xf2, 0x4d, 0x87, 0xe7, 0xeb, 0x30, 0x15, 0x72, - 0x24, 0x9d, 0xfe, 0x81, 0xc4, 0xee, 0xdb, 0xa5, 0xc4, 0x21, 0x4c, 0x14, 0xdb, 0xf6, 0x81, 0x61, - 0x9b, 0xf8, 0xff, 0x60, 0x79, 0x0f, 0xde, 0xe9, 0x65, 0x2d, 0xf0, 0xae, 0xdc, 0x80, 0xf1, 0xaa, - 0x78, 0xe3, 0x9e, 0xed, 0xc2, 0xa5, 0x3e, 0xe6, 0xbd, 0xd8, 0xa9, 0x29, 0x2a, 0x5c, 0x32, 0x1c, - 0xa7, 0x61, 0xe1, 0x9a, 0x70, 0x34, 0xa6, 0x07, 0x8f, 0x79, 0x2a, 0x32, 0xb6, 0xe9, 0x08, 0x81, - 0xaf, 0x95, 0xb1, 0x88, 0x80, 0xe1, 0xa8, 0x80, 0xa8, 0x95, 0x0d, 0x61, 0xa5, 0x2b, 0xa6, 0xb4, - 0x12, 0x52, 0x8b, 0xa2, 0x6a, 0xbf, 0x14, 0xdf, 0x7d, 0xdb, 0xcd, 0x63, 0xe3, 0x82, 0xc4, 0xce, - 0xc3, 0x8d, 0x1e, 0x21, 0x65, 0xc9, 0xfd, 0x3a, 0x2c, 0xee, 0xc0, 0x6d, 0x8a, 0xc5, 0x96, 0x0b, - 0x5d, 0x96, 0xaf, 0xa0, 0x6a, 0xa5, 0xd7, 0x3d, 0xed, 0xa9, 0xeb, 0xbc, 0x88, 0x95, 0x25, 0x98, - 0x34, 0x18, 0xc3, 0xbc, 0x0d, 0xa6, 0x04, 0x38, 0x21, 0xde, 0x06, 0xd4, 0x06, 0x40, 0xd3, 0xb0, - 0x6c, 0x6e, 0x58, 0xae, 0x8e, 0xf4, 0x00, 0x1d, 0x21, 0x56, 0xf9, 0x02, 0xa6, 0x44, 0x25, 0x47, - 0x1a, 0x26, 0xa6, 0x8e, 0x88, 0x82, 0x7e, 0x2f, 0xae, 0xa0, 0x37, 0xc3, 0x33, 0xfc, 0xba, 0x56, - 0x68, 0xe7, 0x40, 0x47, 0x79, 0xe7, 0x20, 0xdb, 0x3b, 0x8d, 0xe1, 0x96, 0xe2, 0xa6, 0x7b, 0x48, - 0xfb, 0x87, 0x5b, 0x18, 0xe9, 0x5c, 0xfb, 0x0d, 0xa7, 0xbe, 0x4f, 0x6a, 0x52, 0x6f, 0x28, 0x35, - 0x6b, 0x50, 0x4a, 0xe8, 0x5b, 0xe6, 0xea, 0x27, 0x04, 0x57, 0xe4, 0x9c, 0x5d, 0xd1, 0xc6, 0x8a, - 0xcb, 0xdc, 0x83, 0x79, 0x6b, 0x60, 0x5a, 0xda, 0xa8, 0xf2, 0x01, 0x8c, 0x7a, 0x8d, 0xb0, 0x7f, - 0x18, 0x66, 0xfb, 0x19, 0xf4, 0xe2, 0xf8, 0xae, 0xfc, 0x39, 0x0f, 0x26, 0x5d, 0x27, 0xed, 0xd5, - 0xf2, 0x73, 0x30, 0xdb, 0x21, 0x2c, 0x10, 0xbd, 0xfe, 0x5d, 0x06, 0x52, 0x65, 0x66, 0x2a, 0x18, - 0x32, 0xe1, 0x2e, 0xfb, 0xdd, 0x7e, 0xf1, 0xa2, 0xad, 0xa7, 0x56, 0x4c, 0xc6, 0xc9, 0x53, 0xa6, - 0x02, 0xe3, 0xed, 0xf6, 0x74, 0x29, 0x66, 0xb2, 0xa4, 0xb4, 0x5b, 0x49, 0x28, 0x19, 0x60, 0x1f, - 0x20, 0xd4, 0xee, 0x2d, 0xc7, 0xca, 0x0b, 0x30, 0x6d, 0x35, 0x11, 0x26, 0x63, 0xd4, 0xe1, 0x72, - 0xb4, 0x7f, 0x2a, 0xc4, 0xcc, 0x8f, 0x90, 0xda, 0xed, 0xa4, 0xa4, 0x0c, 0xf6, 0x0c, 0x94, 0x1e, - 0x5d, 0xd0, 0xea, 0xc0, 0xbc, 0x87, 0x71, 0xed, 0xde, 0xb9, 0x70, 0x19, 0xfb, 0x73, 0x18, 0x93, - 0x0d, 0xcc, 0x62, 0xcc, 0x12, 0x01, 0xa4, 0xbd, 0x9f, 0x00, 0x92, 0xab, 0x7f, 0x05, 0xd7, 0xba, - 0x9b, 0x86, 0xb8, 0xaf, 0xdd, 0x45, 0x6b, 0x77, 0xcf, 0x43, 0x87, 0x03, 0x77, 0xdf, 0xbd, 0x71, - 0x81, 0xbb, 0xe8, 0xd8, 0xc0, 0xfd, 0xef, 0xd8, 0xaf, 0x61, 0xaa, 0xd7, 0x9d, 0x15, 0xb7, 0x89, - 0x7a, 0xf0, 0xda, 0xfd, 0xf3, 0xf1, 0x32, 0xfc, 0x31, 0x82, 0xa5, 0x44, 0x27, 0xf9, 0x47, 0x71, - 0x55, 0x9a, 0x60, 0x01, 0xed, 0xd1, 0x6b, 0x2e, 0x20, 0x25, 0x73, 0xb8, 0xda, 0xd5, 0x78, 0xc4, - 0x15, 0x59, 0x27, 0xac, 0xdd, 0x39, 0x07, 0x2c, 0xa3, 0x1e, 0xc0, 0x44, 0xe4, 0x14, 0xbf, 0x39, - 0xd0, 0x8e, 0x07, 0x6a, 0xa5, 0x84, 0x60, 0x10, 0x49, 0x1b, 0xf9, 0xe6, 0xe5, 0xf1, 0x0a, 0xda, - 0xaa, 0x3f, 0x3f, 0xcd, 0xa2, 0x93, 0xd3, 0x2c, 0xfa, 0xf3, 0x34, 0x8b, 0x7e, 0x38, 0xcb, 0x0e, - 0x9d, 0x9c, 0x65, 0x87, 0x7e, 0x3f, 0xcb, 0x0e, 0xc1, 0x9c, 0x45, 0xfa, 0xac, 0xb9, 0x8b, 0x3e, - 0xbb, 0x6b, 0x5a, 0xfc, 0xe0, 0x70, 0xbf, 0x58, 0x25, 0xcd, 0x52, 0x1b, 0x5a, 0xb5, 0x48, 0xe8, - 0xa9, 0xf4, 0xb4, 0xfd, 0xf7, 0x0a, 0x6f, 0x39, 0x98, 0xed, 0x8f, 0x8a, 0xff, 0x56, 0xee, 0xfc, - 0x1b, 0x00, 0x00, 0xff, 0xff, 0x61, 0xad, 0x4a, 0x71, 0x58, 0x12, 0x00, 0x00, + // 1132 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x58, 0x4f, 0x6f, 0xe3, 0xc4, + 0x1b, 0xee, 0x34, 0x6d, 0xb7, 0x7d, 0xd3, 0xed, 0xee, 0xba, 0xdd, 0xd6, 0xf1, 0xfe, 0x9a, 0xe6, + 0x97, 0xb6, 0x6c, 0x28, 0xdb, 0x64, 0xdb, 0xfd, 0xa3, 0xb2, 0x42, 0xa0, 0xb6, 0x5a, 0x56, 0xd5, + 0x2a, 0xa8, 0xf2, 0xd2, 0x0b, 0x42, 0x0a, 0x6e, 0x32, 0x72, 0xad, 0x24, 0xb6, 0x99, 0x71, 0xcb, + 0x66, 0x25, 0x24, 0xc4, 0x01, 0x71, 0xe0, 0x00, 0x27, 0x4e, 0x7c, 0x87, 0x1e, 0xf8, 0x0a, 0x48, + 0x7b, 0xac, 0x38, 0x81, 0x84, 0x56, 0xa8, 0x3d, 0xac, 0xc4, 0x07, 0x80, 0x2b, 0xf2, 0xd8, 0x9e, + 0xd8, 0x8e, 0xed, 0x38, 0x94, 0x2d, 0x12, 0xdc, 0x62, 0xcf, 0xf3, 0xce, 0xfb, 0x3c, 0xcf, 0xcc, + 0x3b, 0xf3, 0xc6, 0xb0, 0x60, 0x12, 0xe3, 0x08, 0xeb, 0x8a, 0x5e, 0xc7, 0x15, 0x82, 0x55, 0x8d, + 0x5a, 0xa4, 0x53, 0x39, 0x5a, 0xab, 0x58, 0x4f, 0xcb, 0x26, 0x31, 0x2c, 0x43, 0x98, 0xed, 0x02, + 0xca, 0x1e, 0xa0, 0x7c, 0xb4, 0x26, 0xcd, 0xa8, 0x86, 0x6a, 0x30, 0x48, 0xc5, 0xfe, 0xe5, 0xa0, + 0xa5, 0x5c, 0xdd, 0xa0, 0x6d, 0x83, 0xd6, 0x9c, 0x01, 0xe7, 0xc1, 0x1d, 0x9a, 0x73, 0x9e, 0x2a, + 0x6d, 0xaa, 0xda, 0x09, 0xda, 0x54, 0x75, 0x07, 0x96, 0x63, 0x28, 0xf0, 0x6c, 0x0e, 0x6c, 0x25, + 0x06, 0xa6, 0x1c, 0x5a, 0x07, 0x06, 0xd1, 0x9e, 0x29, 0x96, 0x66, 0xe8, 0x2e, 0x76, 0x31, 0x06, + 0x6b, 0x2a, 0x44, 0x69, 0xbb, 0x84, 0x8a, 0xdf, 0x0c, 0xc3, 0x54, 0x95, 0xaa, 0x32, 0x1b, 0xc7, + 0xe4, 0xbd, 0x77, 0xdf, 0x17, 0x6e, 0xc3, 0x18, 0xd5, 0x54, 0x1d, 0x13, 0x11, 0x15, 0x50, 0x69, + 0x62, 0x4b, 0xfc, 0xf1, 0xfb, 0xd5, 0x19, 0x57, 0xc5, 0x66, 0xa3, 0x41, 0x30, 0xa5, 0x4f, 0x2c, + 0xa2, 0xe9, 0xaa, 0xec, 0xe2, 0x84, 0x7b, 0x90, 0x69, 0xe2, 0x8e, 0x38, 0x5c, 0x40, 0xa5, 0xec, + 0xfa, 0x62, 0x39, 0xda, 0xac, 0xb2, 0xec, 0xfe, 0x7e, 0x8c, 0x3b, 0xb2, 0x8d, 0x17, 0xde, 0x86, + 0x51, 0x62, 0xb4, 0x30, 0x15, 0x33, 0x85, 0x4c, 0x29, 0xbb, 0x5e, 0x8c, 0x0d, 0xb4, 0x41, 0x0f, + 0x75, 0x8b, 0x74, 0xb6, 0x46, 0x9e, 0xbf, 0x58, 0x18, 0x92, 0x9d, 0x30, 0x61, 0x07, 0xae, 0x79, + 0xb0, 0x5a, 0xbd, 0xa5, 0x50, 0x5a, 0xd3, 0x1a, 0xe2, 0x08, 0xe3, 0x3c, 0xff, 0xdb, 0x8b, 0x85, + 0x9c, 0x37, 0xb8, 0x6d, 0x8f, 0xed, 0x34, 0x6e, 0x19, 0x6d, 0xcd, 0xc2, 0x6d, 0xd3, 0xea, 0xc8, + 0x57, 0x42, 0x43, 0x0f, 0xb2, 0x9f, 0xbf, 0x3c, 0x5e, 0x71, 0xe5, 0x14, 0x45, 0x98, 0x0d, 0x5a, + 0x22, 0x63, 0x6a, 0x1a, 0x3a, 0xc5, 0xc5, 0xdf, 0x11, 0x4c, 0x56, 0xa9, 0xfa, 0x88, 0x28, 0xba, + 0x65, 0xb3, 0xba, 0x38, 0xaf, 0x36, 0x60, 0xc4, 0x16, 0x2d, 0x66, 0x0a, 0xa8, 0x34, 0xb5, 0xbe, + 0xd4, 0x2f, 0xce, 0x26, 0x27, 0xb3, 0x08, 0xe1, 0x3e, 0x4c, 0x28, 0x0e, 0x13, 0x4c, 0xc5, 0x91, + 0x42, 0x26, 0x91, 0x65, 0x17, 0x1a, 0xb4, 0x64, 0x16, 0x66, 0xfc, 0xba, 0xb9, 0x21, 0x7f, 0x20, + 0xb8, 0xcc, 0xbc, 0x3a, 0x32, 0x9a, 0xf8, 0x3f, 0xe5, 0xc8, 0x1c, 0x5c, 0x0f, 0x08, 0xe7, 0x96, + 0x7c, 0x89, 0xe0, 0x6a, 0x95, 0xaa, 0x7b, 0x3a, 0xf9, 0x07, 0x6a, 0x2a, 0xc8, 0x51, 0x02, 0x31, + 0xcc, 0x84, 0xd3, 0xfc, 0x0e, 0xb9, 0x02, 0x9c, 0x09, 0xb6, 0x0e, 0x5b, 0xcd, 0x3d, 0xb3, 0xa1, + 0x58, 0x7f, 0x65, 0x05, 0x1f, 0xc2, 0x25, 0xac, 0x5b, 0x44, 0xc3, 0x54, 0x1c, 0x66, 0xa5, 0xbc, + 0xdc, 0x8f, 0xaf, 0xbf, 0x9a, 0xbd, 0xd8, 0x20, 0xf7, 0x05, 0x98, 0x8f, 0xa4, 0xc7, 0x05, 0x9c, + 0x20, 0xc8, 0x56, 0xa9, 0xfa, 0x04, 0xb3, 0x1d, 0x49, 0x2f, 0x6e, 0xe3, 0x3d, 0x86, 0x49, 0x7b, + 0x1b, 0xd5, 0x0e, 0x19, 0x9f, 0x54, 0xa7, 0x97, 0x43, 0xdd, 0xd5, 0x9b, 0x25, 0xfc, 0x4d, 0x48, + 0xf3, 0x75, 0x98, 0xf6, 0x29, 0xe2, 0x4a, 0x7f, 0x46, 0xac, 0xfa, 0x76, 0x89, 0x61, 0x1a, 0x94, + 0x6d, 0xb6, 0xed, 0x03, 0x45, 0x57, 0xf1, 0xbf, 0x41, 0xf2, 0x1e, 0xfc, 0x2f, 0x4a, 0x9a, 0xa7, + 0x5d, 0xb8, 0x01, 0x13, 0x75, 0xf6, 0xc6, 0x3e, 0xdb, 0x99, 0x4a, 0x79, 0xdc, 0x79, 0xb1, 0xd3, + 0x10, 0x44, 0xb8, 0xa4, 0x98, 0x66, 0x4b, 0xc3, 0x0d, 0xa6, 0x68, 0x5c, 0xf6, 0x1e, 0x8b, 0x84, + 0x39, 0xb6, 0x69, 0x32, 0x82, 0xe7, 0x72, 0x2c, 0x40, 0x60, 0x38, 0x48, 0x20, 0x28, 0x65, 0x83, + 0x49, 0xe9, 0xc9, 0xc9, 0xa5, 0xf8, 0xd8, 0xa2, 0x20, 0xdb, 0x8f, 0xd9, 0xba, 0x6f, 0xdb, 0x3e, + 0xb6, 0x2e, 0x88, 0xec, 0x3c, 0xdc, 0x88, 0x48, 0xc9, 0xb7, 0xdc, 0x2f, 0x08, 0x72, 0xb6, 0x18, + 0x4a, 0x8d, 0xba, 0xc6, 0xaa, 0xce, 0x77, 0x5f, 0x5e, 0xdc, 0xbe, 0x8b, 0xbc, 0xe1, 0x33, 0xe7, + 0xbf, 0xe1, 0x17, 0xe1, 0xff, 0xb1, 0xea, 0xb8, 0x07, 0x3f, 0x0c, 0xb3, 0x3e, 0x60, 0x9b, 0xe0, + 0xbf, 0xc1, 0x80, 0x95, 0x28, 0x25, 0xce, 0x0a, 0x85, 0xa9, 0x0a, 0x4b, 0x30, 0xa5, 0x50, 0x8a, + 0xad, 0x90, 0x64, 0x79, 0x92, 0xbd, 0xf5, 0x50, 0x1b, 0x00, 0x6d, 0x45, 0xd3, 0x2d, 0x45, 0xb3, + 0x79, 0x8c, 0xf4, 0xe1, 0xe1, 0xc3, 0x0a, 0x1f, 0xc1, 0x34, 0xab, 0xe6, 0x40, 0xd3, 0x48, 0xc5, + 0x51, 0x56, 0xd4, 0xaf, 0x27, 0x15, 0xf5, 0xa6, 0x3f, 0xc2, 0xad, 0x6d, 0x81, 0x84, 0x07, 0x42, + 0x25, 0x5e, 0x80, 0x7c, 0xb4, 0x8d, 0xfe, 0xb6, 0xea, 0xa6, 0x7d, 0x51, 0xb9, 0x07, 0xbc, 0x1f, + 0x12, 0x9e, 0xfb, 0x15, 0x5b, 0x1f, 0x63, 0x4d, 0xe6, 0x15, 0x59, 0xb3, 0x06, 0x95, 0x94, 0xba, + 0xb9, 0x57, 0xdf, 0x22, 0xb8, 0xc2, 0x63, 0x76, 0x59, 0x2b, 0xcf, 0x1a, 0x1a, 0x07, 0x6c, 0x75, + 0xfa, 0xda, 0xd2, 0x85, 0x0a, 0x6f, 0xc1, 0x98, 0xf3, 0x67, 0xc0, 0x2d, 0xcc, 0x7c, 0x9c, 0x40, + 0x27, 0x8f, 0xab, 0xca, 0x8d, 0x79, 0x30, 0x65, 0x2b, 0xe9, 0xce, 0x56, 0xcc, 0xc1, 0x5c, 0x88, + 0x98, 0x47, 0x7a, 0xfd, 0xab, 0x49, 0xc8, 0x54, 0xa9, 0x2a, 0x60, 0xc8, 0xfa, 0xff, 0x69, 0xbc, + 0x16, 0x97, 0x2f, 0xd8, 0x7e, 0x4b, 0xe5, 0x74, 0x38, 0x7e, 0xd2, 0xd6, 0x60, 0xa2, 0xdb, 0xa2, + 0x2f, 0x25, 0x04, 0x73, 0x94, 0x74, 0x2b, 0x0d, 0x8a, 0x27, 0xd8, 0x07, 0xf0, 0xb5, 0xbc, 0xcb, + 0x89, 0xf4, 0x3c, 0x98, 0xb4, 0x9a, 0x0a, 0xc6, 0x73, 0x34, 0xe1, 0x72, 0xb0, 0x87, 0x2c, 0x25, + 0xc4, 0x07, 0x90, 0xd2, 0xed, 0xb4, 0x48, 0x9e, 0xec, 0x19, 0x08, 0x11, 0x9d, 0xe0, 0x6a, 0x5f, + 0xdf, 0xfd, 0x70, 0xe9, 0xde, 0x40, 0x70, 0x9e, 0xfb, 0x43, 0x18, 0xe7, 0x4d, 0xdc, 0x62, 0xc2, + 0x14, 0x1e, 0x48, 0x7a, 0x23, 0x05, 0x88, 0xcf, 0xfe, 0x09, 0x5c, 0xeb, 0x6d, 0x9c, 0x92, 0x56, + 0xbb, 0x07, 0x2d, 0xdd, 0x1d, 0x04, 0xed, 0x4f, 0xdc, 0xdb, 0x7f, 0x24, 0x25, 0xee, 0x41, 0x27, + 0x26, 0x8e, 0xef, 0x33, 0x3e, 0x85, 0xe9, 0xa8, 0x3b, 0x2b, 0xa9, 0x88, 0x22, 0xf0, 0xd2, 0xfd, + 0xc1, 0xf0, 0x3c, 0xfd, 0x31, 0x82, 0xa5, 0x54, 0x27, 0xf9, 0x3b, 0x49, 0xbb, 0x34, 0xc5, 0x04, + 0xd2, 0xa3, 0x73, 0x4e, 0xc0, 0x29, 0x5b, 0x70, 0xb5, 0xa7, 0xf9, 0x4a, 0xda, 0x64, 0x61, 0xb0, + 0x74, 0x67, 0x00, 0x30, 0xcf, 0xfa, 0x05, 0x82, 0xd9, 0x98, 0x06, 0x6b, 0x2d, 0x69, 0xe1, 0x23, + 0x43, 0xa4, 0x37, 0x07, 0x0e, 0xe1, 0x44, 0x0e, 0x60, 0x32, 0x70, 0x9d, 0xdc, 0xec, 0xeb, 0xab, + 0x03, 0x94, 0x2a, 0x29, 0x81, 0x5e, 0x26, 0x69, 0xf4, 0xb3, 0x97, 0xc7, 0x2b, 0x68, 0xab, 0xf9, + 0xfc, 0x34, 0x8f, 0x4e, 0x4e, 0xf3, 0xe8, 0xd7, 0xd3, 0x3c, 0xfa, 0xfa, 0x2c, 0x3f, 0x74, 0x72, + 0x96, 0x1f, 0xfa, 0xe9, 0x2c, 0x3f, 0x04, 0x39, 0xcd, 0x88, 0x99, 0x73, 0x17, 0x7d, 0x70, 0x57, + 0xd5, 0xac, 0x83, 0xc3, 0xfd, 0x72, 0xdd, 0x68, 0x57, 0xba, 0xa0, 0x55, 0xcd, 0xf0, 0x3d, 0x55, + 0x9e, 0x76, 0xbf, 0x75, 0x59, 0x1d, 0x13, 0xd3, 0xfd, 0x31, 0xf6, 0xa1, 0xeb, 0xce, 0x9f, 0x01, + 0x00, 0x00, 0xff, 0xff, 0x75, 0x7b, 0xef, 0x18, 0xe5, 0x13, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -1473,6 +1589,10 @@ type MsgClient interface { // CancelRoleChange cancels an open pending role change. // Only the original proposer may cancel. CancelRoleChange(ctx context.Context, in *MsgCancelRoleChange, opts ...grpc.CallOption) (*MsgCancelRoleChangeResponse, error) + // AssociateRegistryClass associates or updates the registry class for an existing registry entry + // without modifying any roles. The signer must be the CONTROLLER if one is set, or the scope + // data owner if the NFT is a Provenance Metadata Scope, or the NFT owner otherwise. + AssociateRegistryClass(ctx context.Context, in *MsgAssociateRegistryClass, opts ...grpc.CallOption) (*MsgAssociateRegistryClassResponse, error) // UpdateParams is a governance proposal endpoint for updating the registry module's params, // including the default role authorization policies. The authority must be the governance module // account address. @@ -1586,6 +1706,15 @@ func (c *msgClient) CancelRoleChange(ctx context.Context, in *MsgCancelRoleChang return out, nil } +func (c *msgClient) AssociateRegistryClass(ctx context.Context, in *MsgAssociateRegistryClass, opts ...grpc.CallOption) (*MsgAssociateRegistryClassResponse, error) { + out := new(MsgAssociateRegistryClassResponse) + err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/AssociateRegistryClass", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { out := new(MsgUpdateParamsResponse) err := c.cc.Invoke(ctx, "/provenance.registry.v1.Msg/UpdateParams", in, out, opts...) @@ -1636,6 +1765,10 @@ type MsgServer interface { // CancelRoleChange cancels an open pending role change. // Only the original proposer may cancel. CancelRoleChange(context.Context, *MsgCancelRoleChange) (*MsgCancelRoleChangeResponse, error) + // AssociateRegistryClass associates or updates the registry class for an existing registry entry + // without modifying any roles. The signer must be the CONTROLLER if one is set, or the scope + // data owner if the NFT is a Provenance Metadata Scope, or the NFT owner otherwise. + AssociateRegistryClass(context.Context, *MsgAssociateRegistryClass) (*MsgAssociateRegistryClassResponse, error) // UpdateParams is a governance proposal endpoint for updating the registry module's params, // including the default role authorization policies. The authority must be the governance module // account address. @@ -1679,6 +1812,9 @@ func (*UnimplementedMsgServer) UpdateRegistryClassRoleAuthorization(ctx context. func (*UnimplementedMsgServer) CancelRoleChange(ctx context.Context, req *MsgCancelRoleChange) (*MsgCancelRoleChangeResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CancelRoleChange not implemented") } +func (*UnimplementedMsgServer) AssociateRegistryClass(ctx context.Context, req *MsgAssociateRegistryClass) (*MsgAssociateRegistryClassResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method AssociateRegistryClass not implemented") +} func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") } @@ -1885,6 +2021,24 @@ func _Msg_CancelRoleChange_Handler(srv interface{}, ctx context.Context, dec fun return interceptor(ctx, in, info, handler) } +func _Msg_AssociateRegistryClass_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgAssociateRegistryClass) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).AssociateRegistryClass(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/provenance.registry.v1.Msg/AssociateRegistryClass", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).AssociateRegistryClass(ctx, req.(*MsgAssociateRegistryClass)) + } + return interceptor(ctx, in, info, handler) +} + func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(MsgUpdateParams) if err := dec(in); err != nil { @@ -1952,6 +2106,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "CancelRoleChange", Handler: _Msg_CancelRoleChange_Handler, }, + { + MethodName: "AssociateRegistryClass", + Handler: _Msg_AssociateRegistryClass_Handler, + }, { MethodName: "UpdateParams", Handler: _Msg_UpdateParams_Handler, @@ -2642,6 +2800,78 @@ func (m *MsgCancelRoleChangeResponse) MarshalToSizedBuffer(dAtA []byte) (int, er return len(dAtA) - i, nil } +func (m *MsgAssociateRegistryClass) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAssociateRegistryClass) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAssociateRegistryClass) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.RegistryClassId) > 0 { + i -= len(m.RegistryClassId) + copy(dAtA[i:], m.RegistryClassId) + i = encodeVarintTx(dAtA, i, uint64(len(m.RegistryClassId))) + i-- + dAtA[i] = 0x1a + } + if m.Key != nil { + { + size, err := m.Key.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgAssociateRegistryClassResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgAssociateRegistryClassResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgAssociateRegistryClassResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func (m *MsgCreateRegistryClass) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -3164,6 +3394,36 @@ func (m *MsgCancelRoleChangeResponse) Size() (n int) { return n } +func (m *MsgAssociateRegistryClass) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Key != nil { + l = m.Key.Size() + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.RegistryClassId) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgAssociateRegistryClassResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func (m *MsgCreateRegistryClass) Size() (n int) { if m == nil { return 0 @@ -5076,6 +5336,206 @@ func (m *MsgCancelRoleChangeResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgAssociateRegistryClass) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAssociateRegistryClass: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAssociateRegistryClass: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Key == nil { + m.Key = &RegistryKey{} + } + if err := m.Key.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RegistryClassId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RegistryClassId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgAssociateRegistryClassResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgAssociateRegistryClassResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgAssociateRegistryClassResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgCreateRegistryClass) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 From 0dd4900a11c431815e4b2af6cb9a7b89a6a223f3 Mon Sep 17 00:00:00 2001 From: Valerie Wagner Date: Wed, 22 Jul 2026 14:53:59 -0600 Subject: [PATCH 36/36] fix(registry): restrict AssociateRegistryClass scope auth to PARTY_TYPE_OWNER validateAssociateRegistryClassSigner was accepting any address in scope.Owners regardless of party role, which could allow non-data-owner parties (e.g. PARTY_TYPE_ORIGINATOR, PARTY_TYPE_SERVICER) to associate a registry class. Now requires party.Role == PARTY_TYPE_OWNER. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- x/registry/keeper/keeper_test.go | 41 +++++++++++++++++++++++++++++++- x/registry/keeper/nft.go | 5 ++-- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/x/registry/keeper/keeper_test.go b/x/registry/keeper/keeper_test.go index 676c2f6e21..10e1b060ed 100644 --- a/x/registry/keeper/keeper_test.go +++ b/x/registry/keeper/keeper_test.go @@ -1297,7 +1297,46 @@ func (s *KeeperTestSuite) TestAssociateRegistryClass_ScopeDataOwnerCanAssociate( require.NoError(err, "scope data owner must be allowed to associate a registry class") } -// TestAssociateRegistryClass_EmptyClassIdRejected verifies that an empty registry_class_id is +// TestAssociateRegistryClass_NonOwnerScopePartyRejected verifies that a scope party whose role is +// not PARTY_TYPE_OWNER is rejected, even though they appear in scope.Owners. +func (s *KeeperTestSuite) TestAssociateRegistryClass_NonOwnerScopePartyRejected() { + require := s.Require() + + scopeID := metadatatypes.ScopeMetadataAddress(uuid.New()) + scope := metadatatypes.NewScope( + scopeID, + metadatatypes.MetadataAddress{}, + []metadatatypes.Party{ + // user1 is an originator — not a data owner. + {Address: s.user1, Role: metadatatypes.PartyType_PARTY_TYPE_ORIGINATOR}, + }, + nil, "", false, + ) + require.NoError(s.app.MetadataKeeper.SetScope(s.ctx, *scope)) + + assetClassID := s.validNFTClass.Id + key := &types.RegistryKey{AssetClassId: assetClassID, NftId: scopeID.String()} + + class := types.RegistryClass{ + RegistryClassId: "scope-class", + AssetClassId: assetClassID, + Maintainer: s.user1, + } + require.NoError(s.app.RegistryKeeper.CreateRegistryClass(s.ctx, class)) + require.NoError(s.app.RegistryKeeper.SetRegistry(s.ctx, types.RegistryEntry{ + Key: key, Roles: []types.RolesEntry{}, + })) + + msg := &types.MsgAssociateRegistryClass{ + Signer: s.user1, + Key: key, + RegistryClassId: "scope-class", + } + _, err := keeper.NewMsgServer(s.app.RegistryKeeper).AssociateRegistryClass(s.ctx, msg) + require.Error(err, "scope party with non-OWNER role must be rejected") +} + + // rejected at ValidateBasic. func (s *KeeperTestSuite) TestAssociateRegistryClass_EmptyClassIdRejected() { require := s.Require() diff --git a/x/registry/keeper/nft.go b/x/registry/keeper/nft.go index c31e1e274d..a81ae5a19e 100644 --- a/x/registry/keeper/nft.go +++ b/x/registry/keeper/nft.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" + metadatatypes "github.com/provenance-io/provenance/x/metadata/types" "github.com/provenance-io/provenance/x/registry/types" ) @@ -71,11 +72,11 @@ func (k Keeper) validateAssociateRegistryClassSigner(ctx context.Context, assetC return types.NewErrCodeNFTNotFound(*nftID) } for _, party := range scope.Owners { - if party.Address == signer { + if party.Address == signer && party.Role == metadatatypes.PartyType_PARTY_TYPE_OWNER { return nil } } - return types.NewErrCodeUnauthorized("signer is not a data owner of the scope") + return types.NewErrCodeUnauthorized("signer is not a data owner (PARTY_TYPE_OWNER) of the scope") } return k.ValidateNFTOwner(ctx, assetClassID, nftID, signer) }