From 54de7616e76287a40c49f2895cd7c67f05838438 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Fri, 31 May 2024 12:27:12 -0400 Subject: [PATCH] feat: add authorization list for policy transactions (#2289) --- app/setup_handlers.go | 2 +- changelog.md | 1 + .../zetacore/authority/authorization.proto | 21 + .../zetacore/authority/genesis.proto | 4 +- proto/zetachain/zetacore/authority/tx.proto | 1 + testutil/sample/authority.go | 28 + .../zetacore/authority/authorization_pb.d.ts | 71 +++ .../zetacore/authority/genesis_pb.d.ts | 8 +- .../zetachain/zetacore/authority/index.d.ts | 1 + x/authority/genesis.go | 5 + x/authority/genesis_test.go | 62 +- x/authority/keeper/authorization_list.go | 26 + x/authority/keeper/authorization_list_test.go | 49 ++ x/authority/types/authorization.pb.go | 545 ++++++++++++++++++ x/authority/types/authorizations.go | 131 +++++ x/authority/types/authorizations_test.go | 439 ++++++++++++++ x/authority/types/errors.go | 6 +- x/authority/types/genesis.pb.go | 85 ++- x/authority/types/keys.go | 3 +- x/authority/types/tx.pb.go | 35 +- 20 files changed, 1459 insertions(+), 64 deletions(-) create mode 100644 proto/zetachain/zetacore/authority/authorization.proto create mode 100644 typescript/zetachain/zetacore/authority/authorization_pb.d.ts create mode 100644 x/authority/keeper/authorization_list.go create mode 100644 x/authority/keeper/authorization_list_test.go create mode 100644 x/authority/types/authorization.pb.go create mode 100644 x/authority/types/authorizations.go create mode 100644 x/authority/types/authorizations_test.go diff --git a/app/setup_handlers.go b/app/setup_handlers.go index 2ca71ca0ae..7fe7bc1f8f 100644 --- a/app/setup_handlers.go +++ b/app/setup_handlers.go @@ -129,7 +129,7 @@ func SetupHandlers(app *App) { app.UpgradeKeeper.SetUpgradeHandler( constant.Version, - func(ctx sdk.Context, plan types.Plan, vm module.VersionMap) (module.VersionMap, error) { + func(ctx sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) { app.Logger().Info("Running upgrade handler for " + constant.Version) var err error diff --git a/changelog.md b/changelog.md index 442fcb68f8..4dda5f671e 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,7 @@ * [2287](https://github.com/zeta-chain/node/pull/2287) - implement `MsgUpdateChainInfo` message * [2279](https://github.com/zeta-chain/node/pull/2279) - add a CCTXGateway field to chain static data * [2275](https://github.com/zeta-chain/node/pull/2275) - add ChainInfo singleton state variable in authority +* [2289](https://github.com/zeta-chain/node/pull/2289) - add an authorization list to keep track of all authorizations on the chain ### Refactor diff --git a/proto/zetachain/zetacore/authority/authorization.proto b/proto/zetachain/zetacore/authority/authorization.proto new file mode 100644 index 0000000000..914b6b7528 --- /dev/null +++ b/proto/zetachain/zetacore/authority/authorization.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; +package zetachain.zetacore.authority; + +import "gogoproto/gogo.proto"; +import "zetachain/zetacore/authority/policies.proto"; + +option go_package = "github.com/zeta-chain/zetacore/x/authority/types"; + +// Authorization defines the authorization required to access use a message +// which needs special permissions +message Authorization { + // The URL of the message that needs to be authorized + string msg_url = 1; + // The policy that is authorized to access the message + PolicyType authorized_policy = 2; +} + +// AuthorizationList holds the list of authorizations on zetachain +message AuthorizationList { + repeated Authorization authorizations = 1 [ (gogoproto.nullable) = false ]; +} \ No newline at end of file diff --git a/proto/zetachain/zetacore/authority/genesis.proto b/proto/zetachain/zetacore/authority/genesis.proto index a23416161f..4f7b238aca 100644 --- a/proto/zetachain/zetacore/authority/genesis.proto +++ b/proto/zetachain/zetacore/authority/genesis.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package zetachain.zetacore.authority; import "zetachain/zetacore/authority/policies.proto"; +import "zetachain/zetacore/authority/authorization.proto"; import "zetachain/zetacore/authority/chain_info.proto"; import "gogoproto/gogo.proto"; @@ -10,5 +11,6 @@ option go_package = "github.com/zeta-chain/zetacore/x/authority/types"; // GenesisState defines the authority module's genesis state. message GenesisState { Policies policies = 1 [ (gogoproto.nullable) = false ]; - ChainInfo chain_info = 2 [ (gogoproto.nullable) = false ]; + AuthorizationList authorization_list = 2 [ (gogoproto.nullable) = false ]; + ChainInfo chain_info = 3 [ (gogoproto.nullable) = false ]; } diff --git a/proto/zetachain/zetacore/authority/tx.proto b/proto/zetachain/zetacore/authority/tx.proto index cc4d22a6e6..55509e3172 100644 --- a/proto/zetachain/zetacore/authority/tx.proto +++ b/proto/zetachain/zetacore/authority/tx.proto @@ -3,6 +3,7 @@ package zetachain.zetacore.authority; import "zetachain/zetacore/authority/policies.proto"; import "zetachain/zetacore/authority/chain_info.proto"; +import "zetachain/zetacore/authority/authorization.proto"; import "gogoproto/gogo.proto"; option go_package = "github.com/zeta-chain/zetacore/x/authority/types"; diff --git a/testutil/sample/authority.go b/testutil/sample/authority.go index f24542c7fc..c7e3b7e6b8 100644 --- a/testutil/sample/authority.go +++ b/testutil/sample/authority.go @@ -1,6 +1,8 @@ package sample import ( + "fmt" + "github.com/zeta-chain/zetacore/pkg/chains" authoritytypes "github.com/zeta-chain/zetacore/x/authority/types" ) @@ -37,3 +39,29 @@ func ChainInfo(startChainID int64) authoritytypes.ChainInfo { }, } } + +func AuthorizationList(val string) authoritytypes.AuthorizationList { + return authoritytypes.AuthorizationList{ + Authorizations: []authoritytypes.Authorization{ + { + MsgUrl: fmt.Sprintf("/zetachain/%d%s", 0, val), + AuthorizedPolicy: authoritytypes.PolicyType_groupEmergency, + }, + { + MsgUrl: fmt.Sprintf("/zetachain/%d%s", 1, val), + AuthorizedPolicy: authoritytypes.PolicyType_groupAdmin, + }, + { + MsgUrl: fmt.Sprintf("/zetachain/%d%s", 2, val), + AuthorizedPolicy: authoritytypes.PolicyType_groupOperational, + }, + }, + } +} + +func Authorization() authoritytypes.Authorization { + return authoritytypes.Authorization{ + MsgUrl: "ABC", + AuthorizedPolicy: authoritytypes.PolicyType_groupOperational, + } +} diff --git a/typescript/zetachain/zetacore/authority/authorization_pb.d.ts b/typescript/zetachain/zetacore/authority/authorization_pb.d.ts new file mode 100644 index 0000000000..96178864ea --- /dev/null +++ b/typescript/zetachain/zetacore/authority/authorization_pb.d.ts @@ -0,0 +1,71 @@ +// @generated by protoc-gen-es v1.3.0 with parameter "target=dts" +// @generated from file zetachain/zetacore/authority/authorization.proto (package zetachain.zetacore.authority, syntax proto3) +/* eslint-disable */ +// @ts-nocheck + +import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; +import { Message, proto3 } from "@bufbuild/protobuf"; +import type { PolicyType } from "./policies_pb.js"; + +/** + * Authorization defines the authorization required to access use a message + * which needs special permissions + * + * @generated from message zetachain.zetacore.authority.Authorization + */ +export declare class Authorization extends Message { + /** + * The URL of the message that needs to be authorized + * + * @generated from field: string msg_url = 1; + */ + msgUrl: string; + + /** + * The policy that is authorized to access the message + * + * @generated from field: zetachain.zetacore.authority.PolicyType authorized_policy = 2; + */ + authorizedPolicy: PolicyType; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.authority.Authorization"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): Authorization; + + static fromJson(jsonValue: JsonValue, options?: Partial): Authorization; + + static fromJsonString(jsonString: string, options?: Partial): Authorization; + + static equals(a: Authorization | PlainMessage | undefined, b: Authorization | PlainMessage | undefined): boolean; +} + +/** + * AuthorizationList holds the list of authorizations on zetachain + * + * @generated from message zetachain.zetacore.authority.AuthorizationList + */ +export declare class AuthorizationList extends Message { + /** + * @generated from field: repeated zetachain.zetacore.authority.Authorization authorizations = 1; + */ + authorizations: Authorization[]; + + constructor(data?: PartialMessage); + + static readonly runtime: typeof proto3; + static readonly typeName = "zetachain.zetacore.authority.AuthorizationList"; + static readonly fields: FieldList; + + static fromBinary(bytes: Uint8Array, options?: Partial): AuthorizationList; + + static fromJson(jsonValue: JsonValue, options?: Partial): AuthorizationList; + + static fromJsonString(jsonString: string, options?: Partial): AuthorizationList; + + static equals(a: AuthorizationList | PlainMessage | undefined, b: AuthorizationList | PlainMessage | undefined): boolean; +} + diff --git a/typescript/zetachain/zetacore/authority/genesis_pb.d.ts b/typescript/zetachain/zetacore/authority/genesis_pb.d.ts index 381048e226..1bd7878edb 100644 --- a/typescript/zetachain/zetacore/authority/genesis_pb.d.ts +++ b/typescript/zetachain/zetacore/authority/genesis_pb.d.ts @@ -6,6 +6,7 @@ import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf"; import { Message, proto3 } from "@bufbuild/protobuf"; import type { Policies } from "./policies_pb.js"; +import type { AuthorizationList } from "./authorization_pb.js"; import type { ChainInfo } from "./chain_info_pb.js"; /** @@ -20,7 +21,12 @@ export declare class GenesisState extends Message { policies?: Policies; /** - * @generated from field: zetachain.zetacore.authority.ChainInfo chain_info = 2; + * @generated from field: zetachain.zetacore.authority.AuthorizationList authorization_list = 2; + */ + authorizationList?: AuthorizationList; + + /** + * @generated from field: zetachain.zetacore.authority.ChainInfo chain_info = 3; */ chainInfo?: ChainInfo; diff --git a/typescript/zetachain/zetacore/authority/index.d.ts b/typescript/zetachain/zetacore/authority/index.d.ts index e27cc8abc4..6978a3b26c 100644 --- a/typescript/zetachain/zetacore/authority/index.d.ts +++ b/typescript/zetachain/zetacore/authority/index.d.ts @@ -1,3 +1,4 @@ +export * from "./authorization_pb"; export * from "./chain_info_pb"; export * from "./genesis_pb"; export * from "./policies_pb"; diff --git a/x/authority/genesis.go b/x/authority/genesis.go index 56528a5935..06dbaa7e57 100644 --- a/x/authority/genesis.go +++ b/x/authority/genesis.go @@ -11,6 +11,7 @@ import ( func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { k.SetPolicies(ctx, genState.Policies) k.SetChainInfo(ctx, genState.ChainInfo) + k.SetAuthorizationList(ctx, genState.AuthorizationList) } // ExportGenesis returns the authority module's exported genesis. @@ -21,6 +22,10 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { if found { genesis.Policies = policies } + authorizationList, found := k.GetAuthorizationList(ctx) + if found { + genesis.AuthorizationList = authorizationList + } chainInfo, found := k.GetChainInfo(ctx) if found { diff --git a/x/authority/genesis_test.go b/x/authority/genesis_test.go index f14f4f37c6..59176ae91e 100644 --- a/x/authority/genesis_test.go +++ b/x/authority/genesis_test.go @@ -13,31 +13,39 @@ import ( ) func TestGenesis(t *testing.T) { - genesisState := types.GenesisState{ - Policies: sample.Policies(), - ChainInfo: sample.ChainInfo(42), - } - - // Init - k, ctx := keepertest.AuthorityKeeper(t) - authority.InitGenesis(ctx, *k, genesisState) - - // Check policy is set - policies, found := k.GetPolicies(ctx) - require.True(t, found) - require.Equal(t, genesisState.Policies, policies) - - // Check chain info is set - chainInfo, found := k.GetChainInfo(ctx) - require.True(t, found) - require.Equal(t, genesisState.ChainInfo, chainInfo) - - // Export - got := authority.ExportGenesis(ctx, *k) - require.NotNil(t, got) - - // Compare genesis after init and export - nullify.Fill(&genesisState) - nullify.Fill(got) - require.Equal(t, genesisState, *got) + t.Run("valid genesis", func(t *testing.T) { + genesisState := types.GenesisState{ + Policies: sample.Policies(), + AuthorizationList: sample.AuthorizationList("sample"), + ChainInfo: sample.ChainInfo(42), + } + + // Init + k, ctx := keepertest.AuthorityKeeper(t) + authority.InitGenesis(ctx, *k, genesisState) + + // Check policy is set + policies, found := k.GetPolicies(ctx) + require.True(t, found) + require.Equal(t, genesisState.Policies, policies) + + // Check authorization list is set + authorizationList, found := k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, genesisState.AuthorizationList, authorizationList) + + // Check chain info is set + chainInfo, found := k.GetChainInfo(ctx) + require.True(t, found) + require.Equal(t, genesisState.ChainInfo, chainInfo) + + // Export + got := authority.ExportGenesis(ctx, *k) + require.NotNil(t, got) + + // Compare genesis after init and export + nullify.Fill(&genesisState) + nullify.Fill(got) + require.Equal(t, genesisState, *got) + }) } diff --git a/x/authority/keeper/authorization_list.go b/x/authority/keeper/authorization_list.go new file mode 100644 index 0000000000..0e7fba9163 --- /dev/null +++ b/x/authority/keeper/authorization_list.go @@ -0,0 +1,26 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/zeta-chain/zetacore/x/authority/types" +) + +// SetAuthorizationList sets the authorization list to the store.It returns an error if the list is invalid. +func (k Keeper) SetAuthorizationList(ctx sdk.Context, list types.AuthorizationList) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AuthorizationListKey)) + b := k.cdc.MustMarshal(&list) + store.Set([]byte{0}, b) +} + +// GetAuthorizationList returns the authorization list from the store +func (k Keeper) GetAuthorizationList(ctx sdk.Context) (val types.AuthorizationList, found bool) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.AuthorizationListKey)) + b := store.Get([]byte{0}) + if b == nil { + return val, false + } + k.cdc.MustUnmarshal(b, &val) + return val, true +} diff --git a/x/authority/keeper/authorization_list_test.go b/x/authority/keeper/authorization_list_test.go new file mode 100644 index 0000000000..008be61f25 --- /dev/null +++ b/x/authority/keeper/authorization_list_test.go @@ -0,0 +1,49 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/authority/types" +) + +func TestKeeper_GetAuthorizationList(t *testing.T) { + t.Run("successfully get authorizations list", func(t *testing.T) { + k, ctx := keepertest.AuthorityKeeper(t) + authorizationList := sample.AuthorizationList("sample") + k.SetAuthorizationList(ctx, authorizationList) + list, found := k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, authorizationList, list) + }) + + t.Run("get authorizations list not found", func(t *testing.T) { + k, ctx := keepertest.AuthorityKeeper(t) + list, found := k.GetAuthorizationList(ctx) + require.False(t, found) + require.Equal(t, types.AuthorizationList{}, list) + }) +} + +func TestKeeper_SetAuthorizationList(t *testing.T) { + t.Run("successfully set authorizations list when a list already exists", func(t *testing.T) { + k, ctx := keepertest.AuthorityKeeper(t) + authorizationList := sample.AuthorizationList("sample") + k.SetAuthorizationList(ctx, authorizationList) + + list, found := k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, authorizationList, list) + + newAuthorizationList := sample.AuthorizationList("sample2") + require.NotEqual(t, authorizationList, newAuthorizationList) + k.SetAuthorizationList(ctx, newAuthorizationList) + + list, found = k.GetAuthorizationList(ctx) + require.True(t, found) + require.Equal(t, newAuthorizationList, list) + }) +} diff --git a/x/authority/types/authorization.pb.go b/x/authority/types/authorization.pb.go new file mode 100644 index 0000000000..d8c5fa9292 --- /dev/null +++ b/x/authority/types/authorization.pb.go @@ -0,0 +1,545 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: zetachain/zetacore/authority/authorization.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 + +// Authorization defines the authorization required to access use a message +// which needs special permissions +type Authorization struct { + // The URL of the message that needs to be authorized + MsgUrl string `protobuf:"bytes,1,opt,name=msg_url,json=msgUrl,proto3" json:"msg_url,omitempty"` + // The policy that is authorized to access the message + AuthorizedPolicy PolicyType `protobuf:"varint,2,opt,name=authorized_policy,json=authorizedPolicy,proto3,enum=zetachain.zetacore.authority.PolicyType" json:"authorized_policy,omitempty"` +} + +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_b7303e09de7c755a, []int{0} +} +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 + } +} +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) GetMsgUrl() string { + if m != nil { + return m.MsgUrl + } + return "" +} + +func (m *Authorization) GetAuthorizedPolicy() PolicyType { + if m != nil { + return m.AuthorizedPolicy + } + return PolicyType_groupEmergency +} + +// AuthorizationList holds the list of authorizations on zetachain +type AuthorizationList struct { + Authorizations []Authorization `protobuf:"bytes,1,rep,name=authorizations,proto3" json:"authorizations"` +} + +func (m *AuthorizationList) Reset() { *m = AuthorizationList{} } +func (m *AuthorizationList) String() string { return proto.CompactTextString(m) } +func (*AuthorizationList) ProtoMessage() {} +func (*AuthorizationList) Descriptor() ([]byte, []int) { + return fileDescriptor_b7303e09de7c755a, []int{1} +} +func (m *AuthorizationList) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AuthorizationList) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AuthorizationList.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 *AuthorizationList) XXX_Merge(src proto.Message) { + xxx_messageInfo_AuthorizationList.Merge(m, src) +} +func (m *AuthorizationList) XXX_Size() int { + return m.Size() +} +func (m *AuthorizationList) XXX_DiscardUnknown() { + xxx_messageInfo_AuthorizationList.DiscardUnknown(m) +} + +var xxx_messageInfo_AuthorizationList proto.InternalMessageInfo + +func (m *AuthorizationList) GetAuthorizations() []Authorization { + if m != nil { + return m.Authorizations + } + return nil +} + +func init() { + proto.RegisterType((*Authorization)(nil), "zetachain.zetacore.authority.Authorization") + proto.RegisterType((*AuthorizationList)(nil), "zetachain.zetacore.authority.AuthorizationList") +} + +func init() { + proto.RegisterFile("zetachain/zetacore/authority/authorization.proto", fileDescriptor_b7303e09de7c755a) +} + +var fileDescriptor_b7303e09de7c755a = []byte{ + // 271 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x32, 0xa8, 0x4a, 0x2d, 0x49, + 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x13, 0x4b, 0x4b, 0x32, + 0xf2, 0x8b, 0x32, 0x4b, 0x2a, 0x61, 0xac, 0xaa, 0xc4, 0x92, 0xcc, 0xfc, 0x3c, 0xbd, 0x82, 0xa2, + 0xfc, 0x92, 0x7c, 0x21, 0x19, 0xb8, 0x0e, 0x3d, 0x98, 0x0e, 0x3d, 0xb8, 0x0e, 0x29, 0x91, 0xf4, + 0xfc, 0xf4, 0x7c, 0xb0, 0x42, 0x7d, 0x10, 0x0b, 0xa2, 0x47, 0x4a, 0x1b, 0xaf, 0x2d, 0x05, 0xf9, + 0x39, 0x99, 0xc9, 0x99, 0xa9, 0xc5, 0x10, 0xc5, 0x4a, 0xf5, 0x5c, 0xbc, 0x8e, 0xc8, 0xf6, 0x0a, + 0x89, 0x73, 0xb1, 0xe7, 0x16, 0xa7, 0xc7, 0x97, 0x16, 0xe5, 0x48, 0x30, 0x2a, 0x30, 0x6a, 0x70, + 0x06, 0xb1, 0xe5, 0x16, 0xa7, 0x87, 0x16, 0xe5, 0x08, 0x85, 0x72, 0x09, 0xc2, 0x5c, 0x98, 0x9a, + 0x12, 0x0f, 0x36, 0xa6, 0x52, 0x82, 0x49, 0x81, 0x51, 0x83, 0xcf, 0x48, 0x43, 0x0f, 0x9f, 0x33, + 0xf5, 0x02, 0xc0, 0x6a, 0x43, 0x2a, 0x0b, 0x52, 0x83, 0x04, 0x10, 0x46, 0x40, 0x44, 0x95, 0xf2, + 0xb8, 0x04, 0x51, 0x1c, 0xe0, 0x93, 0x59, 0x5c, 0x22, 0x14, 0xc9, 0xc5, 0x87, 0x12, 0x1a, 0xc5, + 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0xda, 0xf8, 0x2d, 0x42, 0x31, 0xc8, 0x89, 0xe5, 0xc4, + 0x3d, 0x79, 0x86, 0x20, 0x34, 0x83, 0x9c, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, + 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, + 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x17, 0x1c, 0x70, + 0xba, 0x68, 0x61, 0x58, 0x81, 0x14, 0x8a, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0x30, + 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x88, 0x76, 0x6b, 0x6c, 0xd8, 0x01, 0x00, 0x00, +} + +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 m.AuthorizedPolicy != 0 { + i = encodeVarintAuthorization(dAtA, i, uint64(m.AuthorizedPolicy)) + i-- + dAtA[i] = 0x10 + } + if len(m.MsgUrl) > 0 { + i -= len(m.MsgUrl) + copy(dAtA[i:], m.MsgUrl) + i = encodeVarintAuthorization(dAtA, i, uint64(len(m.MsgUrl))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *AuthorizationList) 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 *AuthorizationList) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AuthorizationList) 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] = 0xa + } + } + 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 *Authorization) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.MsgUrl) + if l > 0 { + n += 1 + l + sovAuthorization(uint64(l)) + } + if m.AuthorizedPolicy != 0 { + n += 1 + sovAuthorization(uint64(m.AuthorizedPolicy)) + } + return n +} + +func (m *AuthorizationList) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Authorizations) > 0 { + for _, e := range m.Authorizations { + l = e.Size() + n += 1 + l + sovAuthorization(uint64(l)) + } + } + 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 *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 MsgUrl", 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.MsgUrl = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorizedPolicy", wireType) + } + m.AuthorizedPolicy = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthorization + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.AuthorizedPolicy |= PolicyType(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 +} +func (m *AuthorizationList) 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: AuthorizationList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AuthorizationList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + 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 +} +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/authority/types/authorizations.go b/x/authority/types/authorizations.go new file mode 100644 index 0000000000..0de98a768e --- /dev/null +++ b/x/authority/types/authorizations.go @@ -0,0 +1,131 @@ +package types + +import ( + "fmt" + + "cosmossdk.io/errors" +) + +var ( + // OperationPolicyMessages keeps track of the message URLs that can, by default, only be executed by operational policy address + OperationPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgRefundAbortedCCTX", + "/zetachain.zetacore.crosschain.MsgAbortStuckCCTX", + "/zetachain.zetacore.crosschain.MsgUpdateRateLimiterFlags", + "/zetachain.zetacore.crosschain.MsgWhitelistERC20", + "/zetachain.zetacore.fungible.MsgDeployFungibleCoinZRC20", + "/zetachain.zetacore.fungible.MsgDeploySystemContracts", + "/zetachain.zetacore.fungible.MsgRemoveForeignCoin", + "/zetachain.zetacore.fungible.MsgUpdateZRC20LiquidityCap", + "/zetachain.zetacore.fungible.MsgUpdateZRC20WithdrawFee", + "/zetachain.zetacore.fungible.MsgUnpauseZRC20", + "/zetachain.zetacore.observer.MsgAddObserver", + "/zetachain.zetacore.observer.MsgRemoveChainParams", + "/zetachain.zetacore.observer.MsgResetChainNonces", + "/zetachain.zetacore.observer.MsgUpdateChainParams", + "/zetachain.zetacore.observer.MsgEnableCCTX", + "/zetachain.zetacore.observer.MsgUpdateGasPriceIncreaseFlags", + "/zetachain.zetacore.lightclient.MsgEnableHeaderVerification", + } + // AdminPolicyMessages keeps track of the message URLs that can, by default, only be executed by admin policy address + AdminPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgMigrateTssFunds", + "/zetachain.zetacore.crosschain.MsgUpdateTssAddress", + "/zetachain.zetacore.fungible.MsgUpdateContractBytecode", + "/zetachain.zetacore.fungible.MsgUpdateSystemContract", + "/zetachain.zetacore.observer.MsgUpdateObserver", + } + // EmergencyPolicyMessages keeps track of the message URLs that can, by default, only be executed by emergency policy address + EmergencyPolicyMessages = []string{ + "/zetachain.zetacore.crosschain.MsgAddInboundTracker", + "/zetachain.zetacore.crosschain.MsgAddOutboundTracker", + "/zetachain.zetacore.crosschain.MsgRemoveOutboundTracker", + "/zetachain.zetacore.fungible.MsgPauseZRC20", + "/zetachain.zetacore.observer.MsgUpdateKeygen", + "/zetachain.zetacore.observer.MsgDisableCCTX", + "/zetachain.zetacore.lightclient.MsgDisableHeaderVerification", + } +) + +// DefaultAuthorizationsList list is the list of authorizations that presently exist in the system. +// This is the minimum set of authorizations that are required to be set when the authorization table is deployed +func DefaultAuthorizationsList() AuthorizationList { + authorizations := make( + []Authorization, + len(OperationPolicyMessages)+len(AdminPolicyMessages)+len(EmergencyPolicyMessages), + ) + index := 0 + for _, msgURL := range OperationPolicyMessages { + authorizations[index] = Authorization{ + MsgUrl: msgURL, + AuthorizedPolicy: PolicyType_groupOperational, + } + index++ + } + for _, msgURL := range AdminPolicyMessages { + authorizations[index] = Authorization{ + MsgUrl: msgURL, + AuthorizedPolicy: PolicyType_groupAdmin, + } + index++ + } + for _, msgURL := range EmergencyPolicyMessages { + authorizations[index] = Authorization{ + MsgUrl: msgURL, + AuthorizedPolicy: PolicyType_groupEmergency, + } + index++ + } + + return AuthorizationList{ + Authorizations: authorizations, + } +} + +// SetAuthorization adds the authorization to the list. If the authorization already exists, it updates the policy. +func (a *AuthorizationList) SetAuthorization(authorization Authorization) { + for i, auth := range a.Authorizations { + if auth.MsgUrl == authorization.MsgUrl { + a.Authorizations[i].AuthorizedPolicy = authorization.AuthorizedPolicy + return + } + } + a.Authorizations = append(a.Authorizations, authorization) +} + +// RemoveAuthorization removes the authorization from the list. It does not check if the authorization exists or not. +func (a *AuthorizationList) RemoveAuthorization(authorization Authorization) { + for i, auth := range a.Authorizations { + if auth.MsgUrl == authorization.MsgUrl { + a.Authorizations = append(a.Authorizations[:i], a.Authorizations[i+1:]...) + } + } +} + +// GetAuthorizedPolicy returns the policy for the given message url. If the message url is not found, + +func (a *AuthorizationList) GetAuthorizedPolicy(msgURL string) (PolicyType, error) { + for _, auth := range a.Authorizations { + if auth.MsgUrl == msgURL { + return auth.AuthorizedPolicy, nil + } + } + // Returning first value of enum, can consider adding a default value of `EmptyPolicy` in the enum. + return PolicyType(0), ErrAuthorizationNotFound +} + +// Validate checks if the authorization list is valid. It returns an error if the message url is duplicated with different policies. +// It does not check if the list is empty or not, as an empty list is also considered valid. +func (a *AuthorizationList) Validate() error { + checkMsgUrls := make(map[string]bool) + for _, authorization := range a.Authorizations { + if checkMsgUrls[authorization.MsgUrl] { + return errors.Wrap( + ErrInvalidAuthorizationList, + fmt.Sprintf("duplicate message url: %s", authorization.MsgUrl), + ) + } + checkMsgUrls[authorization.MsgUrl] = true + } + return nil +} diff --git a/x/authority/types/authorizations_test.go b/x/authority/types/authorizations_test.go new file mode 100644 index 0000000000..8bb7ed805a --- /dev/null +++ b/x/authority/types/authorizations_test.go @@ -0,0 +1,439 @@ +package types_test + +import ( + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/zetacore/x/authority/types" + crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" + lightclienttypes "github.com/zeta-chain/zetacore/x/lightclient/types" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestAuthorizationList_SetAuthorizations(t *testing.T) { + tt := []struct { + name string + oldList types.AuthorizationList + addAuthorization types.Authorization + expectedList types.AuthorizationList + }{ + { + name: "set new authorization successfully", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + addAuthorization: types.Authorization{ + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "set new authorization successfully with empty list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{}}, + addAuthorization: types.Authorization{ + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "update existing authorization successfully", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + addAuthorization: types.Authorization{ + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + }}, + }, + { + name: "update existing authorization successfully in the middle of the list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + addAuthorization: types.Authorization{ + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + tc.oldList.SetAuthorization(tc.addAuthorization) + require.Equal(t, tc.expectedList, tc.oldList) + }) + } +} + +func TestAuthorizationList_GetAuthorizations(t *testing.T) { + tt := []struct { + name string + authorizations types.AuthorizationList + getPolicyMsgUrl string + expectedPolicy types.PolicyType + error error + }{ + { + name: "get authorizations successfully", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType_groupOperational, + error: nil, + }, + { + name: "get authorizations successfully for admin policy", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupAdmin, + }, + }}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType_groupAdmin, + error: nil, + }, + { + name: "get authorizations successfully for emergency policy", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + }}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType_groupEmergency, + error: nil, + }, + { + name: "get authorizations fails when msg not found in list", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + getPolicyMsgUrl: "XYZ", + expectedPolicy: types.PolicyType(0), + error: types.ErrAuthorizationNotFound, + }, + { + name: "get authorizations fails when msg not found in list", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{}}, + getPolicyMsgUrl: "ABC", + expectedPolicy: types.PolicyType(0), + error: types.ErrAuthorizationNotFound, + }, + { + name: "get authorizations fails when when queried for empty string", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{}}, + getPolicyMsgUrl: "", + expectedPolicy: types.PolicyType(0), + error: types.ErrAuthorizationNotFound, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + policy, err := tc.authorizations.GetAuthorizedPolicy(tc.getPolicyMsgUrl) + require.ErrorIs(t, err, tc.error) + require.Equal(t, tc.expectedPolicy, policy) + }) + } +} + +func TestAuthorizationList_Validate(t *testing.T) { + tt := []struct { + name string + authorizations types.AuthorizationList + expectedError error + }{ + { + name: "validate successfully", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + expectedError: nil, + }, + { + name: "validate successfully with empty list", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{}}, + expectedError: nil, + }, + { + name: "validate successfully for default list", + authorizations: types.DefaultAuthorizationsList(), + expectedError: nil, + }, + { + name: "validate failed with duplicate msg url with different policies", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupEmergency, + }, + }}, + expectedError: errors.Wrap( + types.ErrInvalidAuthorizationList, + fmt.Sprintf("duplicate message url: %s", "ABC")), + }, + { + name: "validate failed with duplicate msg url with same policies", + authorizations: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + expectedError: errors.Wrap( + types.ErrInvalidAuthorizationList, + fmt.Sprintf("duplicate message url: %s", "ABC")), + }} + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + err := tc.authorizations.Validate() + require.ErrorIs(t, err, tc.expectedError) + }) + } +} + +func TestAuthorizationList_RemoveAuthorizations(t *testing.T) { + tt := []struct { + name string + oldList types.AuthorizationList + removeAuthorization types.Authorization + expectedList types.AuthorizationList + }{ + { + name: "remove authorization successfully", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + removeAuthorization: types.Authorization{ + MsgUrl: "ABC", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "remove authorization successfully in the middle of the list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "XYZ", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + removeAuthorization: types.Authorization{ + MsgUrl: "XYZ", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + { + MsgUrl: "DEF", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + { + name: "do not remove anything when trying to remove from an empty list", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{}}, + removeAuthorization: types.Authorization{ + MsgUrl: "XYZ", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{}}, + }, + { + name: "do not remove anything if authorization not found", + oldList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + removeAuthorization: types.Authorization{ + MsgUrl: "XYZ", + }, + expectedList: types.AuthorizationList{Authorizations: []types.Authorization{ + { + MsgUrl: "ABC", + AuthorizedPolicy: types.PolicyType_groupOperational, + }, + }}, + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + tc.oldList.RemoveAuthorization(tc.removeAuthorization) + require.Equal(t, tc.expectedList, tc.oldList) + }) + } +} + +func TestDefaultAuthorizationsList(t *testing.T) { + t.Run("default authorizations list", func(t *testing.T) { + var OperationalPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgRefundAbortedCCTX{}), + sdk.MsgTypeURL(&crosschaintypes.MsgAbortStuckCCTX{}), + sdk.MsgTypeURL(&crosschaintypes.MsgUpdateRateLimiterFlags{}), + sdk.MsgTypeURL(&crosschaintypes.MsgWhitelistERC20{}), + sdk.MsgTypeURL(&fungibletypes.MsgDeployFungibleCoinZRC20{}), + sdk.MsgTypeURL(&fungibletypes.MsgDeploySystemContracts{}), + sdk.MsgTypeURL(&fungibletypes.MsgRemoveForeignCoin{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateZRC20LiquidityCap{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateZRC20WithdrawFee{}), + sdk.MsgTypeURL(&fungibletypes.MsgUnpauseZRC20{}), + sdk.MsgTypeURL(&observertypes.MsgAddObserver{}), + sdk.MsgTypeURL(&observertypes.MsgRemoveChainParams{}), + sdk.MsgTypeURL(&observertypes.MsgResetChainNonces{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateChainParams{}), + sdk.MsgTypeURL(&observertypes.MsgEnableCCTX{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateGasPriceIncreaseFlags{}), + sdk.MsgTypeURL(&lightclienttypes.MsgEnableHeaderVerification{}), + } + + // EmergencyPolicyMessageList is a list of messages that can be authorized by the emergency policy + var EmergencyPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgAddInboundTracker{}), + sdk.MsgTypeURL(&crosschaintypes.MsgAddOutboundTracker{}), + sdk.MsgTypeURL(&crosschaintypes.MsgRemoveOutboundTracker{}), + sdk.MsgTypeURL(&fungibletypes.MsgPauseZRC20{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateKeygen{}), + sdk.MsgTypeURL(&observertypes.MsgDisableCCTX{}), + sdk.MsgTypeURL(&lightclienttypes.MsgDisableHeaderVerification{}), + } + + // AdminPolicyMessageList is a list of messages that can be authorized by the admin policy + var AdminPolicyMessageList = []string{ + sdk.MsgTypeURL(&crosschaintypes.MsgMigrateTssFunds{}), + sdk.MsgTypeURL(&crosschaintypes.MsgUpdateTssAddress{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateContractBytecode{}), + sdk.MsgTypeURL(&fungibletypes.MsgUpdateSystemContract{}), + sdk.MsgTypeURL(&observertypes.MsgUpdateObserver{}), + } + defaultList := types.DefaultAuthorizationsList() + for _, msgUrl := range OperationalPolicyMessageList { + _, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + policy, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + require.Equal(t, types.PolicyType_groupOperational, policy) + } + for _, msgUrl := range EmergencyPolicyMessageList { + _, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + policy, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + require.Equal(t, types.PolicyType_groupEmergency, policy) + } + for _, msgUrl := range AdminPolicyMessageList { + _, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + policy, err := defaultList.GetAuthorizedPolicy(msgUrl) + require.NoError(t, err) + require.Equal(t, types.PolicyType_groupAdmin, policy) + } + require.Len( + t, + defaultList.Authorizations, + len(OperationalPolicyMessageList)+len(EmergencyPolicyMessageList)+len(AdminPolicyMessageList), + ) + }) +} diff --git a/x/authority/types/errors.go b/x/authority/types/errors.go index 6df6aa26a5..88b44b9887 100644 --- a/x/authority/types/errors.go +++ b/x/authority/types/errors.go @@ -2,4 +2,8 @@ package types import errorsmod "cosmossdk.io/errors" -var ErrUnauthorized = errorsmod.Register(ModuleName, 1102, "sender not authorized") +var ( + ErrUnauthorized = errorsmod.Register(ModuleName, 1102, "sender not authorized") + ErrInvalidAuthorizationList = errorsmod.Register(ModuleName, 1103, "invalid authorization list") + ErrAuthorizationNotFound = errorsmod.Register(ModuleName, 1104, "authorization not found") +) diff --git a/x/authority/types/genesis.pb.go b/x/authority/types/genesis.pb.go index aca80bfed4..c4a9f015bc 100644 --- a/x/authority/types/genesis.pb.go +++ b/x/authority/types/genesis.pb.go @@ -25,8 +25,9 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the authority module's genesis state. type GenesisState struct { - Policies Policies `protobuf:"bytes,1,opt,name=policies,proto3" json:"policies"` - ChainInfo ChainInfo `protobuf:"bytes,2,opt,name=chain_info,json=chainInfo,proto3" json:"chain_info"` + Policies Policies `protobuf:"bytes,1,opt,name=policies,proto3" json:"policies"` + AuthorizationList AuthorizationList `protobuf:"bytes,2,opt,name=authorization_list,json=authorizationList,proto3" json:"authorization_list"` + ChainInfo ChainInfo `protobuf:"bytes,3,opt,name=chain_info,json=chainInfo,proto3" json:"chain_info"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -69,6 +70,13 @@ func (m *GenesisState) GetPolicies() Policies { return Policies{} } +func (m *GenesisState) GetAuthorizationList() AuthorizationList { + if m != nil { + return m.AuthorizationList + } + return AuthorizationList{} +} + func (m *GenesisState) GetChainInfo() ChainInfo { if m != nil { return m.ChainInfo @@ -85,23 +93,25 @@ func init() { } var fileDescriptor_633475075491b169 = []byte{ - // 242 bytes of a gzipped FileDescriptorProto + // 287 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0xaa, 0x4a, 0x2d, 0x49, 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x13, 0x4b, 0x4b, 0x32, 0xf2, 0x8b, 0x32, 0x4b, 0x2a, 0xf5, 0xd3, 0x53, 0xf3, 0x52, 0x8b, 0x33, 0x8b, 0xf5, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x64, 0xe0, 0x6a, 0xf5, 0x60, 0x6a, 0xf5, 0xe0, 0x6a, 0xa5, 0xb4, 0xf1, - 0x9a, 0x54, 0x90, 0x9f, 0x93, 0x99, 0x9c, 0x99, 0x0a, 0x35, 0x4a, 0x4a, 0x17, 0xaf, 0x62, 0xb0, - 0x44, 0x7c, 0x66, 0x5e, 0x5a, 0x3e, 0x54, 0xb9, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, - 0x62, 0x41, 0x44, 0x95, 0x96, 0x31, 0x72, 0xf1, 0xb8, 0x43, 0x5c, 0x18, 0x5c, 0x92, 0x58, 0x92, - 0x2a, 0xe4, 0xc1, 0xc5, 0x01, 0xb3, 0x47, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x4d, 0x0f, - 0x9f, 0x9b, 0xf5, 0x02, 0xa0, 0xaa, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xeb, 0x16, - 0xf2, 0xe1, 0xe2, 0x42, 0x38, 0x42, 0x82, 0x09, 0x6c, 0x96, 0x3a, 0x7e, 0xb3, 0x9c, 0x41, 0x12, - 0x9e, 0x79, 0x69, 0xf9, 0x50, 0xc3, 0x38, 0x93, 0xe1, 0x02, 0x5e, 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, 0x10, 0x65, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, - 0x0b, 0x0e, 0x08, 0x5d, 0xb4, 0x30, 0xa9, 0x40, 0x0a, 0x95, 0x92, 0xca, 0x82, 0xd4, 0xe2, 0x24, - 0x36, 0xb0, 0xdf, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x5f, 0x4d, 0x13, 0xb9, 0x01, - 0x00, 0x00, + 0x9a, 0x54, 0x90, 0x9f, 0x93, 0x99, 0x9c, 0x99, 0x0a, 0x35, 0x4a, 0xca, 0x00, 0xaf, 0x62, 0x28, + 0xab, 0x2a, 0xb1, 0x24, 0x33, 0x3f, 0x0f, 0xaa, 0x43, 0x17, 0xaf, 0x0e, 0xb0, 0x44, 0x7c, 0x66, + 0x5e, 0x5a, 0x3e, 0x54, 0xb9, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, 0x62, 0x41, 0x44, + 0x95, 0x7a, 0x98, 0xb8, 0x78, 0xdc, 0x21, 0x7e, 0x0a, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0xf2, 0xe0, + 0xe2, 0x80, 0xb9, 0x4c, 0x82, 0x51, 0x81, 0x51, 0x83, 0xdb, 0x48, 0x4d, 0x0f, 0x9f, 0x2f, 0xf5, + 0x02, 0xa0, 0xaa, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xeb, 0x16, 0x4a, 0xe1, 0x12, + 0x42, 0x71, 0x76, 0x7c, 0x4e, 0x66, 0x71, 0x89, 0x04, 0x13, 0xd8, 0x4c, 0x7d, 0xfc, 0x66, 0x3a, + 0x22, 0xeb, 0xf3, 0xc9, 0x2c, 0x2e, 0x81, 0x1a, 0x2e, 0x98, 0x88, 0x2e, 0x21, 0xe4, 0xc3, 0xc5, + 0x85, 0xf0, 0xaa, 0x04, 0x33, 0xd8, 0x74, 0x75, 0xfc, 0xa6, 0x3b, 0x83, 0x24, 0x3c, 0xf3, 0xd2, + 0xf2, 0xa1, 0xa6, 0x72, 0x26, 0xc3, 0x05, 0xbc, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, + 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, + 0x8e, 0x21, 0xca, 0x20, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x17, 0x1c, 0xdc, + 0xba, 0x68, 0x21, 0x5f, 0x81, 0x14, 0xf6, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0x10, + 0x36, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x7e, 0x66, 0xd6, 0x94, 0x51, 0x02, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -133,6 +143,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintGenesis(dAtA, i, uint64(size)) } i-- + dAtA[i] = 0x1a + { + size, err := m.AuthorizationList.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- dAtA[i] = 0x12 { size, err := m.Policies.MarshalToSizedBuffer(dAtA[:i]) @@ -166,6 +186,8 @@ func (m *GenesisState) Size() (n int) { _ = l l = m.Policies.Size() n += 1 + l + sovGenesis(uint64(l)) + l = m.AuthorizationList.Size() + n += 1 + l + sovGenesis(uint64(l)) l = m.ChainInfo.Size() n += 1 + l + sovGenesis(uint64(l)) return n @@ -240,6 +262,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { } iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AuthorizationList", 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.AuthorizationList.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ChainInfo", wireType) } diff --git a/x/authority/types/keys.go b/x/authority/types/keys.go index 7593feed3a..13365abf77 100644 --- a/x/authority/types/keys.go +++ b/x/authority/types/keys.go @@ -25,7 +25,8 @@ func KeyPrefix(p string) []byte { const ( // PoliciesKey is the key for the policies store PoliciesKey = "Policies-value-" - + // AuthorizationListKey is the key for the authorization list store + AuthorizationListKey = "AuthorizationList-value-" // ChainInfoKey is the key for the chain info store ChainInfoKey = "ChainInfo-value-" ) diff --git a/x/authority/types/tx.pb.go b/x/authority/types/tx.pb.go index 4dd5469876..ac5c04416b 100644 --- a/x/authority/types/tx.pb.go +++ b/x/authority/types/tx.pb.go @@ -220,28 +220,29 @@ func init() { } var fileDescriptor_42e081863c477116 = []byte{ - // 336 bytes of a gzipped FileDescriptorProto + // 350 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xad, 0x4a, 0x2d, 0x49, 0x4c, 0xce, 0x48, 0xcc, 0xcc, 0xd3, 0x07, 0xb3, 0xf2, 0x8b, 0x52, 0xf5, 0x13, 0x4b, 0x4b, 0x32, 0xf2, 0x8b, 0x32, 0x4b, 0x2a, 0xf5, 0x4b, 0x2a, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, 0x64, 0xe0, 0xca, 0xf4, 0x60, 0xca, 0xf4, 0xe0, 0xca, 0xa4, 0xb4, 0xf1, 0x1a, 0x52, 0x90, 0x9f, 0x93, 0x99, 0x9c, 0x99, 0x5a, 0x0c, 0x31, 0x4a, 0x4a, 0x17, 0xaf, 0x62, 0xb0, 0x44, 0x7c, 0x66, 0x5e, - 0x5a, 0x3e, 0x54, 0xb9, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, 0x62, 0x41, 0x44, 0x95, - 0xca, 0xb9, 0x04, 0x7d, 0x8b, 0xd3, 0x43, 0x0b, 0x52, 0x12, 0x4b, 0x52, 0x03, 0xa0, 0xe6, 0x0b, - 0x49, 0x70, 0xb1, 0x27, 0x17, 0xa5, 0x26, 0x96, 0xe4, 0x17, 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, - 0x06, 0xc1, 0xb8, 0x42, 0x1e, 0x5c, 0x1c, 0x30, 0x57, 0x48, 0x30, 0x29, 0x30, 0x6a, 0x70, 0x1b, - 0xa9, 0xe9, 0xe1, 0xf3, 0x91, 0x1e, 0xcc, 0x4c, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0xe0, - 0xba, 0x95, 0xa4, 0xb9, 0x24, 0x31, 0x2c, 0x0e, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x55, - 0xaa, 0xe1, 0x12, 0x82, 0x4b, 0x3a, 0x83, 0x8c, 0xf6, 0xcc, 0x4b, 0xcb, 0xc7, 0xe3, 0x2c, 0x1f, - 0x2e, 0x2e, 0x84, 0x7f, 0xa1, 0x0e, 0x53, 0xc7, 0xef, 0x30, 0xb8, 0xb1, 0x50, 0x97, 0x71, 0x26, - 0xc3, 0x04, 0x94, 0x64, 0xb8, 0xa4, 0x30, 0x6d, 0x87, 0xb9, 0xcd, 0xa8, 0x81, 0x89, 0x8b, 0xd9, - 0xb7, 0x38, 0x5d, 0xa8, 0x8a, 0x8b, 0x0f, 0x2d, 0xd8, 0xf4, 0xf1, 0xdb, 0x88, 0xe1, 0x5d, 0x29, - 0x73, 0x12, 0x35, 0xc0, 0xdc, 0x20, 0x54, 0xcb, 0xc5, 0x8f, 0x1e, 0x38, 0x06, 0x44, 0x9a, 0x05, - 0xd7, 0x21, 0x65, 0x41, 0xaa, 0x0e, 0x98, 0xf5, 0x4e, 0x5e, 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, 0x10, 0x65, 0x90, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, 0x0b, - 0x4e, 0x94, 0xba, 0x68, 0xe9, 0xb3, 0x02, 0x39, 0x4f, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, - 0xd3, 0xa1, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x42, 0xb9, 0x95, 0xf6, 0x40, 0x03, 0x00, 0x00, + 0x5a, 0x3e, 0x54, 0xb9, 0x01, 0x5e, 0xe5, 0x50, 0x56, 0x55, 0x62, 0x49, 0x66, 0x7e, 0x1e, 0x54, + 0x87, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x98, 0xa9, 0x0f, 0x62, 0x41, 0x44, 0x95, 0xca, 0xb9, 0x04, + 0x7d, 0x8b, 0xd3, 0x43, 0x0b, 0x52, 0x12, 0x4b, 0x52, 0x03, 0xa0, 0x2e, 0x12, 0x92, 0xe0, 0x62, + 0x4f, 0x2e, 0x4a, 0x4d, 0x2c, 0xc9, 0x2f, 0x92, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0c, 0x82, 0x71, + 0x85, 0x3c, 0xb8, 0x38, 0x60, 0xee, 0x96, 0x60, 0x52, 0x60, 0xd4, 0xe0, 0x36, 0x52, 0xd3, 0xc3, + 0x17, 0x06, 0x7a, 0x30, 0x33, 0x9d, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x82, 0xeb, 0x56, 0x92, + 0xe6, 0x92, 0xc4, 0xb0, 0x38, 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0xa9, 0x86, 0x4b, + 0x08, 0x2e, 0xe9, 0x0c, 0x32, 0xda, 0x33, 0x2f, 0x2d, 0x1f, 0x8f, 0xb3, 0x7c, 0xb8, 0xb8, 0x10, + 0x21, 0x04, 0x75, 0x98, 0x3a, 0x7e, 0x87, 0xc1, 0x8d, 0x85, 0xba, 0x8c, 0x33, 0x19, 0x26, 0xa0, + 0x24, 0xc3, 0x25, 0x85, 0x69, 0x3b, 0xcc, 0x6d, 0x46, 0x0d, 0x4c, 0x5c, 0xcc, 0xbe, 0xc5, 0xe9, + 0x42, 0x55, 0x5c, 0x7c, 0x68, 0xc1, 0xa6, 0x8f, 0xdf, 0x46, 0x0c, 0xef, 0x4a, 0x99, 0x93, 0xa8, + 0x01, 0xe6, 0x06, 0xa1, 0x5a, 0x2e, 0x7e, 0xf4, 0xc0, 0x31, 0x20, 0xd2, 0x2c, 0xb8, 0x0e, 0x29, + 0x0b, 0x52, 0x75, 0xc0, 0xac, 0x77, 0xf2, 0x3a, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, + 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, + 0x86, 0x28, 0x83, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, 0xbd, 0xe4, 0xfc, 0x5c, 0x70, 0xba, 0xd4, + 0x45, 0x4b, 0xa2, 0x15, 0xc8, 0xb9, 0xa8, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x9c, 0x0e, 0x8d, + 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe8, 0xeb, 0xa6, 0x40, 0x72, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used.