From eb48a8106d26740244026d72853cc548ceee260f Mon Sep 17 00:00:00 2001 From: Zanicar <38106253+zanicar@users.noreply.github.com> Date: Fri, 6 Jan 2023 18:31:09 +0200 Subject: [PATCH] Docs: add module specs (#278) * patch: docs (spec): updated documentation - fixed incorrect comments; - added some missing comments; - removed superflous comments; - updated airdrop spec; - added claimsmanager spec; - added spec template for interchainqueries; - fixed event emission in interchainstaking; - added interchainstaking spec; - added spec module template; - added participationrewards spec; * patch: docs: update module specifications --- .../quicksilver/claimsmanager/v1/query.proto | 8 +- .../participationrewards/v1/query.proto | 1 + x/airdrop/spec/README.md | 56 +- x/claimsmanager/keeper/claims.go | 8 +- x/claimsmanager/spec/README.md | 211 +++++ x/interchainquery/spec/README.md | 168 ++++ .../keeper/proposal_handler.go | 2 +- x/interchainstaking/spec/README.md | 891 ++++++++++++++++++ x/module/spec/README.md | 71 ++ x/participationrewards/client/cli/tx.go | 2 +- x/participationrewards/keeper/submodule.go | 2 + x/participationrewards/spec/README.md | 423 +++++++++ x/participationrewards/types/protocol_data.go | 1 + .../types/submodule_liquid.go | 12 +- .../types/submodule_osmosis.go | 2 + 15 files changed, 1822 insertions(+), 36 deletions(-) create mode 100644 x/claimsmanager/spec/README.md create mode 100644 x/interchainquery/spec/README.md create mode 100644 x/interchainstaking/spec/README.md create mode 100644 x/module/spec/README.md create mode 100644 x/participationrewards/spec/README.md diff --git a/proto/quicksilver/claimsmanager/v1/query.proto b/proto/quicksilver/claimsmanager/v1/query.proto index cfe6b63d1..caf5a585d 100644 --- a/proto/quicksilver/claimsmanager/v1/query.proto +++ b/proto/quicksilver/claimsmanager/v1/query.proto @@ -11,31 +11,35 @@ option go_package = "github.com/ingenuity-build/quicksilver/x/claimsmanager/type // Query provides defines the gRPC querier service. service Query { - // Params returns the total set of participation rewards parameters. - + // Claims returns all zone claims from the current epoch. rpc Claims(QueryClaimsRequest) returns (QueryClaimsResponse) { option (google.api.http).get = "/quicksilver/claimsmanager/v1/claims/{chain_id}"; } + // LastEpochClaims returns all zone claims from the last epoch. rpc LastEpochClaims(QueryClaimsRequest) returns (QueryClaimsResponse) { option (google.api.http).get = "/quicksilver/claimsmanager/v1/previous_epoch_claims/{chain_id}"; } + // UserClaims returns all zone claims for a given address from the current epoch. rpc UserClaims(QueryClaimsRequest) returns (QueryClaimsResponse) { option (google.api.http).get = "/quicksilver/claimsmanager/v1/user/{address}/claims"; } + // UserLastEpochClaims returns all zone claims for a given address from the last epoch. rpc UserLastEpochClaims(QueryClaimsRequest) returns (QueryClaimsResponse) { option (google.api.http).get = "/quicksilver/claimsmanager/v1/user/{address}/previous_epoch_claims"; } } +// QueryClaimsRequest is the request type for the Query/Claims RPC method. message QueryClaimsRequest { string chain_id = 1 [ (gogoproto.moretags) = "yaml:\"chain_id\"" ]; string address = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; cosmos.base.query.v1beta1.PageRequest pagination = 3; } +// QueryClaimsResponse is the response type for the Query/Claims RPC method. message QueryClaimsResponse { repeated Claim claims = 1 [ (gogoproto.nullable) = false ]; cosmos.base.query.v1beta1.PageResponse pagination = 2; diff --git a/proto/quicksilver/participationrewards/v1/query.proto b/proto/quicksilver/participationrewards/v1/query.proto index 4b3093435..73952df98 100644 --- a/proto/quicksilver/participationrewards/v1/query.proto +++ b/proto/quicksilver/participationrewards/v1/query.proto @@ -15,6 +15,7 @@ service Query { "/quicksilver/participationrewards/v1/params"; } + // ProtocolData returns the requested protocol data. rpc ProtocolData(QueryProtocolDataRequest) returns (QueryProtocolDataResponse) { option (google.api.http).get = diff --git a/x/airdrop/spec/README.md b/x/airdrop/spec/README.md index 43c4f4948..4fb078d10 100644 --- a/x/airdrop/spec/README.md +++ b/x/airdrop/spec/README.md @@ -68,7 +68,7 @@ Any claims completed are recorded against the `ClaimRecord` and claimed amounts ### Action -``` +```go // Action is used as an enum to denote specific actions or tasks. type Action int32 @@ -128,7 +128,7 @@ var Action_value = map[string]int32{ ### Status -``` +```go // Status is used as an enum to denote zone status. type Status int32 @@ -153,7 +153,7 @@ var Status_value = map[string]int32{ ### ZoneDrop -``` +```go KeyPrefixZoneDrop = []byte{0x01} func GetKeyZoneDrop(chainID string) []byte { @@ -174,7 +174,7 @@ type ZoneDrop struct { ### ClaimRecord -``` +```go KeyPrefixClaimRecord = []byte{0x02} func GetKeyClaimRecord(chainID string, addr sdk.AccAddress) []byte { @@ -199,7 +199,7 @@ type ClaimRecord struct { ### CompletedAction -``` +```go // CompletedAction represents a claim action completed by the user. type CompletedAction struct { CompleteTime time.Time `protobuf:"bytes,1,opt,name=complete_time,json=completeTime,proto3,stdtime" json:"complete_time" yaml:"complete_time"` @@ -211,7 +211,7 @@ type CompletedAction struct { Description of message types that trigger state transitions; -``` +```protobuf // Msg defines the airdrop Msg service. service Msg { rpc Claim(MsgClaim) returns (MsgClaimResponse) { @@ -227,14 +227,12 @@ service Msg { Claim the airdrop for the given action in the given zone. -Use: `claim [chainID] [action]` - -``` +```go type MsgClaim struct { - ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty" yaml:"chain_id"` - Action int32 `protobuf:"varint,2,opt,name=action,proto3" json:"action,omitempty" yaml:"action"` - Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty" yaml:"address"` - Proof []byte `protobuf:"bytes,4,opt,name=proof,proto3" json:"proof,omitempty" yaml:"proof"` + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty" yaml:"chain_id"` + Action int64 `protobuf:"varint,2,opt,name=action,proto3" json:"action,omitempty" yaml:"action"` + Address string `protobuf:"bytes,3,opt,name=address,proto3" json:"address,omitempty" yaml:"address"` + Proofs []*types.Proof `protobuf:"bytes,4,rep,name=proofs,proto3" json:"proofs,omitempty" yaml:"proofs"` } type MsgClaimResponse struct { @@ -246,6 +244,16 @@ type MsgClaimResponse struct { Description of transactions that collect messages in specific contexts to trigger state transitions; +### claim + +Claim airdrop for the given action in the given zone. + +`claim [chainID] [action]` + +Example: + +`$ quicksilverd tx airdrop claim cosmoshub-4 ActionDelegateStake` + ## Events Events emitted by module for tracking messages and index transactions; @@ -268,13 +276,13 @@ Events emitted by module for tracking messages and index transactions; ## Hooks -Description of hook functions that may be used by other modules to execute operations at specific points within this module; +N/A ## Queries Description of available information request queries; -``` +```protobuf service Query { // Params returns the total set of airdrop parameters. rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { @@ -316,7 +324,7 @@ Query the current airdrop module parameters. Use: `params` -``` +```go // QueryParamsRequest is the request type for the Query/Params RPC method. type QueryParamsRequest struct { } @@ -334,7 +342,7 @@ Query the airdrop details of the specified zone. Use: `zone [chain_id]` -``` +```go // QueryZoneDropRequest is the request type for Query/ZoneDrop RPC method. type QueryZoneDropRequest struct { // chain_id identifies the zone. @@ -353,7 +361,7 @@ Returns the airdrop module account balance of the specified zone. Use: `account-balance [chain_id]` -``` +```go // QueryAccountBalanceRequest is the request type for Query/AccountBalance RPC // method. type QueryAccountBalanceRequest struct { @@ -374,7 +382,7 @@ Query all airdrops of the specified status. Use: `zone-drops [status]` -``` +```go // QueryZoneDropsRequest is the request type for Query/ZoneDrops RPC method. type QueryZoneDropsRequest struct { // status enables to query zone airdrops matching a given status: @@ -398,7 +406,7 @@ Query airdrop claim record details of the given address for the given zone. Use: `claim-record [chain_id] [address]` -``` +```go // QueryClaimRecordRequest is the request type for Query/ClaimRecord RPC method. type QueryClaimRecordRequest struct { ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty" yaml:"chain_id"` @@ -414,7 +422,7 @@ type QueryClaimRecordResponse struct { ## Keepers -Keepers exposed by module; +https://pkg.go.dev/github.com/ingenuity-build/quicksilver/x/airdrop/keeper ## Parameters @@ -431,7 +439,7 @@ Description of parameters: Register a zone airdrop proposal. -``` +```go type RegisterZoneDropProposal struct { Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"` Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"` @@ -442,11 +450,9 @@ type RegisterZoneDropProposal struct { ## Begin Block -Description of logic executed with optional methods or external hooks; +N/A ## End Block -Description of logic executed with optional methods or external hooks; - At the end of every block the module iterates through all unconcluded airdrops (expired but not yet concluded) and calls `EndZoneDrop` for each instance, that deletes all associated `ClaimRecord`s. diff --git a/x/claimsmanager/keeper/claims.go b/x/claimsmanager/keeper/claims.go index b00f12bde..73cbccb42 100644 --- a/x/claimsmanager/keeper/claims.go +++ b/x/claimsmanager/keeper/claims.go @@ -180,7 +180,7 @@ func (k Keeper) IterateAllLastEpochClaims(ctx sdk.Context, fn func(index int64, } } -// IterateUserClaims iterates through zone claims for a given address. +// IterateAllClaims iterates through all claims. func (k Keeper) IterateAllClaims(ctx sdk.Context, fn func(index int64, key []byte, data types.Claim) (stop bool)) { // noop if fn == nil { @@ -257,7 +257,7 @@ func (k Keeper) AllZoneLastEpochUserClaims(ctx sdk.Context, chainID string, addr return claims } -// ClearClaims deletes all the claims of the given zone. +// ClearClaims deletes all the current epoch claims of the given zone. func (k Keeper) ClearClaims(ctx sdk.Context, chainID string) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.GetPrefixClaim(chainID)) @@ -269,7 +269,7 @@ func (k Keeper) ClearClaims(ctx sdk.Context, chainID string) { } } -// ClearClaims deletes all the claims of the given zone. +// ClearLastEpochClaims deletes all the last epoch claims of the given zone. func (k Keeper) ClearLastEpochClaims(ctx sdk.Context, chainID string) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, types.GetPrefixLastEpochClaim(chainID)) @@ -281,7 +281,7 @@ func (k Keeper) ClearLastEpochClaims(ctx sdk.Context, chainID string) { } } -// ClearClaims deletes all the claims of the given zone. +// ArchiveAndGarbageCollectClaims deletes all the last epoch claims and moves the current epoch claims to the last epoch store. func (k Keeper) ArchiveAndGarbageCollectClaims(ctx sdk.Context, chainID string) { k.ClearLastEpochClaims(ctx, chainID) diff --git a/x/claimsmanager/spec/README.md b/x/claimsmanager/spec/README.md new file mode 100644 index 000000000..beb67f150 --- /dev/null +++ b/x/claimsmanager/spec/README.md @@ -0,0 +1,211 @@ +# Claims Manager + +## Abstract + +Module, `x/claimsmanager`, provides storage and retrieval mechanisms for proof +based claims utilized in other modules. + +## Contents + +1. [Concepts](#Concepts) +1. [State](#State) +1. [Messages](#Messages) +1. [Transactions](#Transactions) +1. [Events](#Events) +1. [Hooks](#Hooks) +1. [Queries](#Queries) +1. [Keepers](#Keepers) +1. [Parameters](#Parameters) +1. [Begin Block](#Begin-Block) +1. [End Block](#End-Block) + +## Concepts + +`x/claimsmanager` is simply a data store for use by other modules to avoid +unnecessary or circular dependencies. + +## State + +### ClaimType + +```go +const ( + // Undefined action (per protobuf spec) + ClaimTypeUndefined ClaimType = 0 + ClaimTypeLiquidToken ClaimType = 1 + ClaimTypeOsmosisPool ClaimType = 2 + ClaimTypeCrescentPool ClaimType = 3 + ClaimTypeSifchainPool ClaimType = 4 +) + +var ClaimType_name = map[int32]string{ + 0: "ClaimTypeUndefined", + 1: "ClaimTypeLiquidToken", + 2: "ClaimTypeOsmosisPool", + 3: "ClaimTypeCrescentPool", + 4: "ClaimTypeSifchainPool", +} + +var ClaimType_value = map[string]int32{ + "ClaimTypeUndefined": 0, + "ClaimTypeLiquidToken": 1, + "ClaimTypeOsmosisPool": 2, + "ClaimTypeCrescentPool": 3, + "ClaimTypeSifchainPool": 4, +} +``` + +### Claim + +```go +KeyPrefixClaim = []byte{0x00} +KeyPrefixLastEpochClaim = []byte{0x01} + +// ClaimKey returns the key for storing a given claim. +func GetGenericKeyClaim(key []byte, chainID string, address string, module ClaimType, srcChainID string) []byte { + typeBytes := make([]byte, 4) + binary.BigEndian.PutUint32(typeBytes, uint32(module)) + key = append(key, []byte(chainID)...) + key = append(key, byte(0x00)) + key = append(key, []byte(address)...) + key = append(key, typeBytes...) + return append(key, []byte(srcChainID)...) +} + +func GetKeyClaim(chainID string, address string, module ClaimType, srcChainID string) []byte { + return GetGenericKeyClaim(KeyPrefixClaim, chainID, address, module, srcChainID) +} + +func GetPrefixClaim(chainID string) []byte { + return append(KeyPrefixClaim, []byte(chainID)...) +} + +func GetPrefixUserClaim(chainID string, address string) []byte { + key := KeyPrefixClaim + key = append(key, []byte(chainID)...) + key = append(key, byte(0x00)) + key = append(key, []byte(address)...) + return key +} + +func GetKeyLastEpochClaim(chainID string, address string, module ClaimType, srcChainID string) []byte { + return GetGenericKeyClaim(KeyPrefixLastEpochClaim, chainID, address, module, srcChainID) +} + +func GetPrefixLastEpochClaim(chainID string) []byte { + return append(KeyPrefixLastEpochClaim, []byte(chainID)...) +} + +func GetPrefixLastEpochUserClaim(chainID string, address string) []byte { + key := KeyPrefixLastEpochClaim + key = append(key, []byte(chainID)...) + key = append(key, byte(0x00)) + key = append(key, []byte(address)...) + return key +} + +// Claim define the users claim for holding assets within a given zone. +type Claim struct { + UserAddress string `protobuf:"bytes,1,opt,name=user_address,json=userAddress,proto3" json:"user_address,omitempty"` + ChainId string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + Module ClaimType `protobuf:"varint,3,opt,name=module,proto3,enum=quicksilver.claimsmanager.v1.ClaimType" json:"module,omitempty"` + SourceChainId string `protobuf:"bytes,4,opt,name=source_chain_id,json=sourceChainId,proto3" json:"source_chain_id,omitempty"` + Amount uint64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"` +} +``` + +### Proof + +```go +// Proof defines a type used to cryptographically prove a claim. +type Proof struct { + Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` + ProofOps *crypto.ProofOps `protobuf:"bytes,3,opt,name=proof_ops,proto3" json:"proof_ops,omitempty"` + Height int64 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` + ProofType string `protobuf:"bytes,5,opt,name=proof_type,proto3" json:"proof_type,omitempty"` +} +``` + +## Messages + +Description of message types that trigger state transitions; + +`x/claimsmanager` does not alter state directly, it rather provides the mechanism for other modules to do so. + +## Transactions + +Description of transactions that collect messages in specific contexts to trigger state transitions; + +`x/claimsmanager` does not provide any transactions, it is the responsibility of calling modules to do so. + +## Events + +N/A + +## Hooks + +N/A + +## Queries + +Description of available information request queries; + +```protobuf +// Query provides defines the gRPC querier service. +service Query { + // Claims returns all zone claims from the current epoch. + rpc Claims(QueryClaimsRequest) returns (QueryClaimsResponse) { + option (google.api.http).get = "/quicksilver/claimsmanager/v1/claims/{chain_id}"; + } + + // LastEpochClaims returns all zone claims from the last epoch. + rpc LastEpochClaims(QueryClaimsRequest) returns (QueryClaimsResponse) { + option (google.api.http).get = "/quicksilver/claimsmanager/v1/previous_epoch_claims/{chain_id}"; + } + + // UserClaims returns all zone claims for a given address from the current epoch. + rpc UserClaims(QueryClaimsRequest) returns (QueryClaimsResponse) { + option (google.api.http).get = "/quicksilver/claimsmanager/v1/user/{address}/claims"; + } + + // UserLastEpochClaims returns all zone claims for a given address from the last epoch. + rpc UserLastEpochClaims(QueryClaimsRequest) returns (QueryClaimsResponse) { + option (google.api.http).get = "/quicksilver/claimsmanager/v1/user/{address}/previous_epoch_claims"; + } +} +``` + +The above queries use the following `Request` and `Response` types: + +```go +// QueryClaimsRequest is the request type for the Query/Claims RPC method. +type QueryClaimsRequest struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty" yaml:"chain_id"` + Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` + Pagination *query.PageRequest `protobuf:"bytes,3,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +// QueryClaimsResponse is the response type for the Query/Claims RPC method. +type QueryClaimsResponse struct { + Claims []Claim `protobuf:"bytes,1,rep,name=claims,proto3" json:"claims"` + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} +``` + +## Keepers + +https://pkg.go.dev/github.com/ingenuity-build/quicksilver/x/claimsmanager/keeper + +## Parameters + +N/A + +## Begin Block + +N/A + +## End Block + +N/A + diff --git a/x/interchainquery/spec/README.md b/x/interchainquery/spec/README.md new file mode 100644 index 000000000..53df9a0d7 --- /dev/null +++ b/x/interchainquery/spec/README.md @@ -0,0 +1,168 @@ +# Interchain Query + +## Abstract + +Module, `x/interchainquery`, defines and implements the mechanisms to +facilitate provable cross-chain queries. + +## Contents + +1. [Concepts](#Concepts) +1. [State](#State) +1. [Messages](#Messages) +1. [Transactions](#Transactions) +1. [Events](#Events) +1. [Hooks](#Hooks) +1. [Queries](#Queries) +1. [Keepers](#Keepers) +1. [Parameters](#Parameters) +1. [Begin Block](#Begin-Block) +1. [End Block](#End-Block) + +## Concepts + +[needs ellaboration] + +## State + +### Query + +```go +type Query struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ConnectionId string `protobuf:"bytes,2,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + ChainId string `protobuf:"bytes,3,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + QueryType string `protobuf:"bytes,4,opt,name=query_type,json=queryType,proto3" json:"query_type,omitempty"` + Request []byte `protobuf:"bytes,5,opt,name=request,proto3" json:"request,omitempty"` + // change these to uint64 in v0.5.0 + Period github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=period,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"period"` + LastHeight github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=last_height,json=lastHeight,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"last_height"` + CallbackId string `protobuf:"bytes,8,opt,name=callback_id,json=callbackId,proto3" json:"callback_id,omitempty"` + Ttl uint64 `protobuf:"varint,9,opt,name=ttl,proto3" json:"ttl,omitempty"` + LastEmission github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,10,opt,name=last_emission,json=lastEmission,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"last_emission"` +} +``` + +### DataPoint + +```go +type DataPoint struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // change these to uint64 in v0.5.0 + RemoteHeight github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=remote_height,json=remoteHeight,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"remote_height"` + LocalHeight github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=local_height,json=localHeight,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"local_height"` + Value []byte `protobuf:"bytes,4,opt,name=value,proto3" json:"result,omitempty"` +} +``` + +## Messages + +Description of message types that trigger state transitions; + +```protobuf +service Msg { + // SubmitQueryResponse defines a method for submit query responses. + rpc SubmitQueryResponse(MsgSubmitQueryResponse) + returns (MsgSubmitQueryResponseResponse) { + option (google.api.http) = { + post : "/interchainquery/tx/v1beta1/submitquery" + body : "*" + }; + }; +} +``` + +### MsgSubmitQueryResponse + +MsgSubmitQueryResponse is used to signal a response from a remote chain. + +```go +// MsgSubmitQueryResponse represents a message type to fulfil a query request. +type MsgSubmitQueryResponse struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty" yaml:"chain_id"` + QueryId string `protobuf:"bytes,2,opt,name=query_id,json=queryId,proto3" json:"query_id,omitempty" yaml:"query_id"` + Result []byte `protobuf:"bytes,3,opt,name=result,proto3" json:"result,omitempty" yaml:"result"` + ProofOps *crypto.ProofOps `protobuf:"bytes,4,opt,name=proof_ops,json=proofOps,proto3" json:"proof_ops,omitempty" yaml:"proof_ops"` + Height int64 `protobuf:"varint,5,opt,name=height,proto3" json:"height,omitempty" yaml:"height"` + FromAddress string `protobuf:"bytes,6,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` +} +``` + +* **ChainId** - the chain id of the remote chain; +* **QueryId** - the query id that solicited this response; +* **Result** - the encoded query response from the remote chain; +* **ProofOps** - the cryptographic proofs related to this response; +* **Height** - the block height of the remote chain at the time of response; +* **FromAddress** - ; + +## Transactions + +N/A + +## Events + +Events emitted by module for tracking messages and index transactions; + +### EndBlocker + +| Type | Attribute Key | Attribute Value | +|:--------|:--------------|:------------------| +| message | module | interchainquery | +| message | query_id | {query_id} | +| message | chain_id | {chain_id} | +| message | connection_id | {connection_id} | +| message | type | {query_type} | +| message | height | "0" | +| message | request | {request} | + +## Hooks + +N/A + +## Queries + +Description of available information request queries; + +```protobuf +service QuerySrvr { + // Params returns the total set of minting parameters. + rpc Queries(QueryRequestsRequest) returns (QueryRequestsResponse) { + option (google.api.http).get = + "/quicksilver/interchainquery/v1/queries/{chain_id}"; + } +} +``` + +### queries + +Query the existing IBC queries of the module. + +```go +type QueryRequestsRequest struct { + Pagination *query.PageRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` + ChainId string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` +} + +type QueryRequestsResponse struct { + // params defines the parameters of the module. + Queries []Query `protobuf:"bytes,1,rep,name=queries,proto3" json:"queries"` + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} +``` + +## Keepers + +https://pkg.go.dev/github.com/ingenuity-build/quicksilver/x/interchainquery/keeper + +## Parameters + +N/A + +## Begin Block + +N/A + +## End Block + +* Iterate through all queries and emit events for periodic queries. +* Iterate through all data points to perform garbage collection. diff --git a/x/interchainstaking/keeper/proposal_handler.go b/x/interchainstaking/keeper/proposal_handler.go index 4f5c8f140..3f8c49ada 100644 --- a/x/interchainstaking/keeper/proposal_handler.go +++ b/x/interchainstaking/keeper/proposal_handler.go @@ -98,7 +98,7 @@ func HandleRegisterZoneProposal(ctx sdk.Context, k Keeper, p *types.RegisterZone sdk.NewEvent( types.EventTypeRegisterZone, sdk.NewAttribute(types.AttributeKeyConnectionID, p.ConnectionId), - sdk.NewAttribute(types.AttributeKeyConnectionID, chainID), + sdk.NewAttribute(types.AttributeKeyRecipientChain, chainID), ), }) diff --git a/x/interchainstaking/spec/README.md b/x/interchainstaking/spec/README.md new file mode 100644 index 000000000..e2f61dfb5 --- /dev/null +++ b/x/interchainstaking/spec/README.md @@ -0,0 +1,891 @@ +# Interchain Staking + +## Abstract + +Module, `x/interchainstaking`, defines and implements the core Quicksilver +protocol. + +## Contents + +1. [Concepts](#Concepts) +1. [State](#State) +1. [Messages](#Messages) +1. [Transactions](#Transactions) +1. [Proposals](#Proposals) +1. [Events](#Events) +1. [Hooks](#Hooks) +1. [Queries](#Queries) +1. [Keepers](#Keepers) +1. [Parameters](#Parameters) +1. [Begin Block](#Begin-Block) +1. [After Epoch End](#After-Epoch-End) +1. [IBC](#IBC) + +## Concepts + +Key concepts, mechanisms and core logic for the module; + +### Registered Zone + +A `RegisteredZone` is the core record kept by the Interchain Staking module. It +is created when a `CreateRegisteredZone` proposal has been passed by +governance. + +It keeps track of the chain ID, and related IBC connection, Interchain Accounts +managed by Quicksilver of the host chain (for the purposes of Deposit, +Delegation, Withdrawal and Performance monitoring), the host chain's +validatorset, `aggregate_intent` and the `redemption_rate` used when minting +and burning qAssets for native assets. + +### Redemption Rate + +The `redemption_rate` is the ratio between qAsset supply, tracked by the Bank +module, and the total number of native assets staked against a given zone. This +ratio is used to determine how many qAssets to mint when staking with +Quicksilver, to ensure everyone joins the pool with the correct proportion. +Additionally, the previous epoch's redemption_rate is tracked, and used to +calculate the number of tokens to unbond when redeeming qAssets. The minimum of +the the current and last rates are used to negate an attack whereby a user can +enter the protocol immediately before the epoch, and exit immediately after to +claim a disproportionate amount of rewards for a very short exposure to the +protocol. + +### Intent Signalling + +Intent Signalling is the mechanism by which users of the protocol are able to +Signal to which validators they will their proportion of the stake pool is +delegated. In order to maintain fungibility of qAssets, we must pool assets and +delegate them as a single entity. Users are able to signal to which validators, +and with what weightings, they wish their proportion of stake to be delegated. +This is aggregated on an epochly basis. + +### Aggregate Intent + +The Aggregate Intent is calculated epochly, based upon the Signaled Intent from +each user, and the weight given to that intent based upon the assets the user +holds (this information is drawn from the Claimsmanager module). This aggregate +itnent is used as a target, for the protocol to use when determining where to +allocate assets during delegation, rebalance and undelegation processes. + +### Interchain Accounts + +## State + +### Zone + +A `Zone` represents a Cosmos based blockchain that integrates with the +Quicksilver protocol via Interchain Accounts (ICS) and Interblockchain +Communication (IBC). + +```go +type Zone struct { + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + ChainId string `protobuf:"bytes,2,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + DepositAddress *ICAAccount `protobuf:"bytes,3,opt,name=deposit_address,json=depositAddress,proto3" json:"deposit_address,omitempty"` + WithdrawalAddress *ICAAccount `protobuf:"bytes,4,opt,name=withdrawal_address,json=withdrawalAddress,proto3" json:"withdrawal_address,omitempty"` + PerformanceAddress *ICAAccount `protobuf:"bytes,5,opt,name=performance_address,json=performanceAddress,proto3" json:"performance_address,omitempty"` + DelegationAddress *ICAAccount `protobuf:"bytes,6,opt,name=delegation_address,json=delegationAddress,proto3" json:"delegation_address,omitempty"` + AccountPrefix string `protobuf:"bytes,7,opt,name=account_prefix,json=accountPrefix,proto3" json:"account_prefix,omitempty"` + LocalDenom string `protobuf:"bytes,8,opt,name=local_denom,json=localDenom,proto3" json:"local_denom,omitempty"` + BaseDenom string `protobuf:"bytes,9,opt,name=base_denom,json=baseDenom,proto3" json:"base_denom,omitempty"` + RedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,10,opt,name=redemption_rate,json=redemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"redemption_rate"` + LastRedemptionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,11,opt,name=last_redemption_rate,json=lastRedemptionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"last_redemption_rate"` + Validators []*Validator `protobuf:"bytes,12,rep,name=validators,proto3" json:"validators,omitempty"` + AggregateIntent ValidatorIntents `protobuf:"bytes,13,rep,name=aggregate_intent,json=aggregateIntent,proto3,castrepeated=ValidatorIntents" json:"aggregate_intent,omitempty"` + MultiSend bool `protobuf:"varint,14,opt,name=multi_send,json=multiSend,proto3" json:"multi_send,omitempty"` + LiquidityModule bool `protobuf:"varint,15,opt,name=liquidity_module,json=liquidityModule,proto3" json:"liquidity_module,omitempty"` + WithdrawalWaitgroup uint32 `protobuf:"varint,16,opt,name=withdrawal_waitgroup,json=withdrawalWaitgroup,proto3" json:"withdrawal_waitgroup,omitempty"` + IbcNextValidatorsHash []byte `protobuf:"bytes,17,opt,name=ibc_next_validators_hash,json=ibcNextValidatorsHash,proto3" json:"ibc_next_validators_hash,omitempty"` + ValidatorSelectionAllocation uint64 `protobuf:"varint,18,opt,name=validator_selection_allocation,json=validatorSelectionAllocation,proto3" json:"validator_selection_allocation,omitempty"` + HoldingsAllocation uint64 `protobuf:"varint,19,opt,name=holdings_allocation,json=holdingsAllocation,proto3" json:"holdings_allocation,omitempty"` + LastEpochHeight int64 `protobuf:"varint,20,opt,name=last_epoch_height,json=lastEpochHeight,proto3" json:"last_epoch_height,omitempty"` + Tvl github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,21,opt,name=tvl,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"tvl"` + UnbondingPeriod int64 `protobuf:"varint,22,opt,name=unbonding_period,json=unbondingPeriod,proto3" json:"unbonding_period,omitempty"` +} +``` + +* **ConnectionId** - remote zone connection identifier; +* **ChainId** - remote zone identifier; +* **DepositAddress** - remote zone deposit address; +* **WithdrawalAddress** - remote zone withdrawal address; +* **PerformanceAddress** - remote zone performance address (each validator gets + an exact equal delegation from this account to measure performance); +* **DelegationAddresses** - remote zone delegation addresses to represent + granular voting power; +* **AccountPrefix** - remote zone account address prefix; +* **LocalDenom** - protocol denomination (qAsset), e.g. uqatom; +* **BaseDenom** - remote zone denomination (uStake), e.g. uatom; +* **RedemptionRate** - redemption rate between protocol qAsset and native + remote asset; +* **LastRedemptionRate** - redemption rate as at previous epoch boundary + (used to prevent epoch boundary gaming); +* **Validators** - list of validators on the remote zone; +* **AggregateIntent** - the aggregated delegation intent of the protocol for + this remote zone. The map key is the corresponding validator address + contained in the `ValidatorIntent`; +* **MultiSend** - multisend support on remote zone; +* **LiquidityModule** - liquidity module enabled on remote zone; +* **WithdrawalWaitgroup** - tally of pending withdrawal transactions; +* **IbcNextValidatorHash** - +* **ValidatorSelectionAllocation** - proportional zone rewards allocation for + validator selection; +* **HoldingsAllocation** - proportional zone rewards allocation for asset + holdings; +* **LastEpochHeight** - the height of this chain at the last Quicksilver epoch + boundary; +* **Tvl** - the Total Value Locked for this zone (in terms of Atom value); +* **UnbondingPeriod** - this zone's unbonding period; + +### ICAAccount + +An `ICAAccount` represents an account on an remote zone under the control of +the protocol. + +```go +type ICAAccount struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // balance defines the different coins this balance holds. + Balance github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=balance,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"balance"` + PortName string `protobuf:"bytes,3,opt,name=port_name,json=portName,proto3" json:"port_name,omitempty"` + WithdrawalAddress string `protobuf:"bytes,4,opt,name=withdrawal_address,json=withdrawalAddress,proto3" json:"withdrawal_address,omitempty"` + BalanceWaitgroup uint32 `protobuf:"varint,5,opt,name=balance_waitgroup,json=balanceWaitgroup,proto3" json:"balance_waitgroup,omitempty"` +} +``` + +* **Address** - the account address on the remote zone; +* **Balance** - the account balance on the remote zone; +* **PortName** - the port name to access the remote zone; +* **WithdrawalAddress** - the address withdrawals are sent to for this account; +* **BalanceWaitgroup** - the tally of pending balance query transactions sent + to the remote zone; + +### Distribution + +```go +type Distribution struct { + Valoper string `protobuf:"bytes,1,opt,name=valoper,proto3" json:"valoper,omitempty"` + Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` +} +``` + +### WithdrawalRecord + +```go +type WithdrawalRecord struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + Delegator string `protobuf:"bytes,2,opt,name=delegator,proto3" json:"delegator,omitempty"` + Distribution []*Distribution `protobuf:"bytes,3,rep,name=distribution,proto3" json:"distribution,omitempty"` + Recipient string `protobuf:"bytes,4,opt,name=recipient,proto3" json:"recipient,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,5,rep,name=amount,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount"` + BurnAmount github_com_cosmos_cosmos_sdk_types.Coin `protobuf:"bytes,6,opt,name=burn_amount,json=burnAmount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Coin" json:"burn_amount"` + Txhash string `protobuf:"bytes,7,opt,name=txhash,proto3" json:"txhash,omitempty"` + Status int32 `protobuf:"varint,8,opt,name=status,proto3" json:"status,omitempty"` + CompletionTime time.Time `protobuf:"bytes,9,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time"` +} +``` + +### UnbondingRecord + +```go +type UnbondingRecord struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + EpochNumber int64 `protobuf:"varint,2,opt,name=epoch_number,json=epochNumber,proto3" json:"epoch_number,omitempty"` + Validator string `protobuf:"bytes,3,opt,name=validator,proto3" json:"validator,omitempty"` + RelatedTxhash []string `protobuf:"bytes,4,rep,name=related_txhash,json=relatedTxhash,proto3" json:"related_txhash,omitempty"` +} +``` + +### RedelegationRecord + +```go +type RedelegationRecord struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + EpochNumber int64 `protobuf:"varint,2,opt,name=epoch_number,json=epochNumber,proto3" json:"epoch_number,omitempty"` + Source string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"` + Destination string `protobuf:"bytes,4,opt,name=destination,proto3" json:"destination,omitempty"` + Amount int64 `protobuf:"varint,5,opt,name=amount,proto3" json:"amount,omitempty"` + CompletionTime time.Time `protobuf:"bytes,6,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time"` +} +``` + +### TransferRecord + +```go +type TransferRecord struct { + Sender string `protobuf:"bytes,1,opt,name=sender,proto3" json:"sender,omitempty"` + Recipient string `protobuf:"bytes,2,opt,name=recipient,proto3" json:"recipient,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Coin `protobuf:"bytes,3,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Coin" json:"amount"` +} +``` + +### Validator + +`Validator` represents relevant meta data of a validator within a zone. + +```go +type Validator struct { + ValoperAddress string `protobuf:"bytes,1,opt,name=valoper_address,json=valoperAddress,proto3" json:"valoper_address,omitempty"` + CommissionRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=commission_rate,json=commissionRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"commission_rate"` + DelegatorShares github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=delegator_shares,json=delegatorShares,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"delegator_shares"` + VotingPower github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=voting_power,json=votingPower,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"voting_power"` + Score github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=score,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"score"` + Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` + Jailed bool `protobuf:"varint,7,opt,name=jailed,proto3" json:"jailed,omitempty"` + Tombstoned bool `protobuf:"varint,8,opt,name=tombstoned,proto3" json:"tombstoned,omitempty"` + JailedSince time.Time `protobuf:"bytes,9,opt,name=jailed_since,json=jailedSince,proto3,stdtime" json:"jailed_since"` +} +``` + +* **ValoperAddress** - the validator address; +* **CommissionRate** - the validator commission rate; +* **DelegatorShares** - +* **VotingPower** - the validator voting power on the remote zone; +* **Score** - the validator Quicksilver protocol overall score; +* **Status** - +* **Jailed** - is this validator currently jailed; +* **Tombstoned** - is this validator tombstoned; +* **JailedSince** - blocktime timestamp when this validator was jailed; + +### ValidatorIntent + +`ValidatorIntent` represents the weighted delegation intent to a particular +validator. + +```go +type ValidatorIntent struct { + ValoperAddress string `protobuf:"bytes,1,opt,name=valoper_address,proto3" json:"valoper_address,omitempty"` + Weight github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=weight,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"weight" yaml:"weight"` +} +``` + +* **ValoperAddress** - the remote zone validator address; +* **Weight** - the weight of intended delegation to this validator; + +### DelegatorIntent + +`DelegatorIntent` represents the current delegation intent for this zone. +Delegations are incrementally adjusted towards the `Zone.AggregateIntent`. + +```go +type DelegatorIntent struct { + Delegator string `protobuf:"bytes,1,opt,name=delegator,proto3" json:"delegator,omitempty"` + Intents ValidatorIntents `protobuf:"bytes,2,rep,name=intents,proto3,castrepeated=ValidatorIntents" json:"intents,omitempty"` +} +``` + +* **Delegator** - the delegation account address on the remote zone; +* **Intents** - the delegation intents to individual validators on the remote + zone; + +### Delegation + +`Delegation` represents the actual delegations made by +`RegisteredZone.DelegationAddresses` to validators on the remote zone; + +```go +type Delegation struct { + DelegationAddress string `protobuf:"bytes,1,opt,name=delegation_address,json=delegationAddress,proto3" json:"delegation_address,omitempty"` + ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Coin `protobuf:"bytes,3,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Coin" json:"amount"` + Height int64 `protobuf:"varint,4,opt,name=height,proto3" json:"height,omitempty"` + RedelegationEnd int64 `protobuf:"varint,5,opt,name=redelegation_end,json=redelegationEnd,proto3" json:"redelegation_end,omitempty"` +} +``` + +* **DelegationAddress** - the delegator address on the remote zone; +* **ValidatorAddress** - the validator address on the remote zone; +* **Amount** - the amount delegated; +* **Height** - the block height at which the delegation occured; +* **RedelegationEnd** - ; + +### PortConnectionTuple + +```go +type PortConnectionTuple struct { + ConnectionId string `protobuf:"bytes,1,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"` + PortId string `protobuf:"bytes,2,opt,name=port_id,json=portId,proto3" json:"port_id,omitempty"` +} +``` + +### Receipt + +```go +type Receipt struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` + Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` + Txhash string `protobuf:"bytes,3,opt,name=txhash,proto3" json:"txhash,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=amount,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount"` +} +``` + +## Messages + +```protobuf +// Msg defines the interchainstaking Msg service. +service Msg { + // RequestRedemption defines a method for requesting burning of qAssets for + // native assets. + rpc RequestRedemption(MsgRequestRedemption) + returns (MsgRequestRedemptionResponse) { + option (google.api.http) = { + post : "/quicksilver/tx/v1/interchainstaking/redeem" + body : "*" + }; + }; + // SignalIntent defines a method for signalling voting intent for one or more + // validators. + rpc SignalIntent(MsgSignalIntent) returns (MsgSignalIntentResponse) { + option (google.api.http) = { + post : "/quicksilver/tx/v1/interchainstaking/intent" + body : "*" + }; + }; +} +``` + +### MsgRequestRedemption + +Redeems the indicated qAsset coin amount from the protocol, converting the +qAsset back to the native asset at the appropriate redemption rate. + +```go +// MsgRequestRedemption represents a message type to request a burn of qAssets +// for native assets. +type MsgRequestRedemption struct { + Value types.Coin `protobuf:"bytes,1,opt,name=value,proto3" json:"value" yaml:"coin"` + DestinationAddress string `protobuf:"bytes,2,opt,name=destination_address,json=destinationAddress,proto3" json:"destination_address,omitempty"` + FromAddress string `protobuf:"bytes,3,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` +} +``` + +* **Value** - qAsset as standard cosmos sdk cli coin string, {amount}{denomination}; +* **DestinationAddress** - standard cosmos sdk bech32 address string; +* **FromAddress** - standard cosmos sdk bech32 address string; + +**Transaction**: [`redeem`](#redeem) + +### MsgSignalIntent + +Signal validator delegation intent for a given zone by weight. + +```go +// MsgSignalIntent represents a message type for signalling voting intent for +// one or more validators. +type MsgSignalIntent struct { + ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty" yaml:"chain_id"` + Intents string `protobuf:"bytes,2,opt,name=intents,proto3" json:"intents,omitempty" yaml:"intents"` + FromAddress string `protobuf:"bytes,3,opt,name=from_address,json=fromAddress,proto3" json:"from_address,omitempty"` +} +``` + +* **ChainId** - zone identifier string; +* **Intents** - list of validator intents according to weight; +* **FromAddress** - standard cosmos sdk bech32 address string; + +**Transaction**: [`signal-intent`](#signal-intent) + +## Transactions + +### signal-intent + +Signal validator delegation intent by providing a comma seperated string +containing a decimal weight and the bech32 validator address. + +`quicksilverd signal-intent [chain_id] [delegation_intent]` + +Example: + +`quicksilverd signal-intent cosmoshub-4 0.3cosmosvaloper1xxxxxxxxx,0.3cosmosvaloper1yyyyyyyyy,0.4cosmosvaloper1zzzzzzzzz` + +### redeem + +Redeem qAssets for native tokens. + +`quicksilverd redeem [coins] [destination_address]` + +Example: + +`quicksilverd redeem 2500000uatom cosmos1pgfzn0zhxjjgte7hprwtnqyhrn534lqk437x2w` + +## Proposals + +### register-zone + +Submit a zone registration proposal. + +`quicksilverd register-zone [proposal-file]` + +The proposal must include an initial deposit and the details must be provided +as a json file, e.g. + +```json +{ + "title": "Register cosmoshub-4", + "description": "Onboard the cosmoshub-4 zone to Quicksilver", + "connection_id": "connection-3", + "base_denom": "uatom", + "local_denom": "uqatom", + "account_prefix": "cosmos", + "multi_send": true, + "liquidity_module": false, + "deposit": "512000000uqck" +} +``` + +### update-zone + +Submit a zone update proposal. + +`quicksilverd update-zone [proposal-file]` + +The proposal must include a deposit and the details must be provided as a json +file, e.g. + +```json +{ + "title": "Enable liquidity module for cosmoshub-4", + "description": "Update cosmoshub-4 to enable liquidity module", + "chain_id": "cosmoshub-4", + "changes": [{ + "key": "liquidity_module", + "value": "true", + }], + "deposit": "512000000uqck" +} +``` + +## Events + +Events emitted by module for tracking messages and index transactions; + +### RegisterZone + +| Type | Attribute Key | Attribute Value | +|:--------------|:--------------|:------------------| +| message | module | interchainstaking | +| register_zone | connection_id | {connection_id} | +| register_zone | chain_id | {chain_id} | + +### MsgClaim + +| Type | Attribute Key | Attribute Value | +|:-------------------|:--------------|:------------------| +| message | module | interchainstaking | +| request_redemption | burn_amount | {burn_amount} | +| request_redemption | redeem_amount | {redeem_amount} | +| request_redemption | recipient | {recipient} | +| request_redemption | chain_id | {chain_id} | +| request_redemption | connection_id | {connection_id} | + +## Hooks + +N/A + +## Queries + +```protobuf +service Query { + // ZoneInfos provides meta data on connected zones. + rpc ZoneInfos(QueryZonesInfoRequest) returns (QueryZonesInfoResponse) { + option (google.api.http).get = "/quicksilver/interchainstaking/v1/zones"; + } + // DepositAccount provides data on the deposit address for a connected zone. + rpc DepositAccount(QueryDepositAccountForChainRequest) + returns (QueryDepositAccountForChainResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/zones/{chain_id}/deposit_address"; + } + // DelegatorIntent provides data on the intent of the delegator for the given + // zone. + rpc DelegatorIntent(QueryDelegatorIntentRequest) + returns (QueryDelegatorIntentResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/zones/{chain_id}/delegator_intent/" + "{delegator_address}"; + } + + // Delegations provides data on the delegations for the given zone. + rpc Delegations(QueryDelegationsRequest) returns (QueryDelegationsResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/zones/{chain_id}/delegations"; + } + + // Delegations provides data on the delegations for the given zone. + rpc Receipts(QueryReceiptsRequest) returns (QueryReceiptsResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/zones/{chain_id}/receipts"; + } + + // WithdrawalRecords provides data on the active withdrawals. + rpc ZoneWithdrawalRecords(QueryWithdrawalRecordsRequest) + returns (QueryWithdrawalRecordsResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/zones/{chain_id}/withdrawal_records/{delegator_address}"; + } + + // WithdrawalRecords provides data on the active withdrawals. + rpc WithdrawalRecords(QueryWithdrawalRecordsRequest) + returns (QueryWithdrawalRecordsResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/withdrawal_records"; + } + + // UnbondingRecords provides data on the active unbondings. + rpc UnbondingRecords(QueryUnbondingRecordsRequest) + returns (QueryUnbondingRecordsResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/unbonding_records"; + } + + // RedelegationRecords provides data on the active unbondings. + rpc RedelegationRecords(QueryRedelegationRecordsRequest) + returns (QueryRedelegationRecordsResponse) { + option (google.api.http).get = + "/quicksilver/interchainstaking/v1/redelegation_records"; + } +} +``` + +### zones + +Query registered zones. + +`quicksilverd query interchainstaking zones` + +Example response: + +```yaml +pagination: + next_key: null + total: "2" +zones: +- account_prefix: cosmos + aggregate_intent: {} + base_denom: uatom + chain_id: lstest-1 + connection_id: connection-0 + delegation_addresses: + - address: cosmos12hww50r7q7xyhspt72c9c8n3uyknqhv208sxuq9mcqdqjv0mcreq62maa2 + balance: [] + balance_waitgroup: 0 + delegated_balance: + amount: "25083333" + denom: uatom + port_name: icacontroller-lstest-1.delegate.9 + ... + deposit_address: + address: cosmos146xjrj2tass9fvtcw30dtl9v8f4t26z7cjxlxw0paxyyxmx2hqcq73vk6p + balance: + - amount: "25000000" + denom: cosmosvaloper16pxh2v4hr28h2gkntgfk8qgh47pfmjfhvcamkc3 + balance_waitgroup: 0 + delegated_balance: + amount: "0" + denom: uatom + port_name: icacontroller-lstest-1.deposit + holdings_allocation: [] + ibc_next_validators_hash: Qn4t+8M6bod6ewSYwnPScdWwwbSE7mc47GlMpuo15d0= + last_redemption_rate: "1.000000000000000000" + liquidity_module: true + local_denom: uqatom + multi_send: true + performance_address: + address: cosmos1yp64sfc5d4g4xtemptachyd2jaraxz8c5vptp7swgnv86l3ll3yqzz72wk + balance: [] + balance_waitgroup: 0 + delegated_balance: + amount: "0" + denom: uatom + port_name: icacontroller-lstest-1.performance + redemption_rate: "1.000000000000000000" + validator_selection_allocation: [] + validators: + - commission_rate: "0.030000000000000000" + delegator_shares: "4000093333.000000000000000000" + score: "0.000000000000000000" + valoper_address: cosmosvaloper12evgzwsc2av7nfc5x7p74g9ppmfwm30xug6pwv + voting_power: "4000093333" + ... + withdrawal_address: + address: cosmos1w7x78xu4ms3qwspryl8jjy57l3esns8ayh6mj9g3544wmgnfnzrs86lr9p + balance: [] + balance_waitgroup: 0 + delegated_balance: + amount: "0" + denom: uatom + port_name: icacontroller-lstest-1.withdrawal + withdrawal_waitgroup: 12 +- account_prefix: osmo + aggregate_intent: {} + base_denom: uosmo + chain_id: lstest-2 + connection_id: connection-1 + delegation_addresses: [] + deposit_address: + address: osmo14s68pery7n8s9cm6lzwxv4s0ppucctv28fcmtqg852965hfgpuvsmq5edm + balance: [] + balance_waitgroup: 0 + delegated_balance: + amount: "0" + denom: uosmo + port_name: icacontroller-lstest-2.deposit + holdings_allocation: [] + ibc_next_validators_hash: TyRByZjTIrfQ81mMlvoRyg1crPx4Kk9Lur+Kkty06h8= + last_redemption_rate: "1.000000000000000000" + liquidity_module: true + local_denom: uqosmo + multi_send: true + performance_address: + address: osmo1v799s4plwuyux8xunmzxcw6y2g8t5u373ravsy2zxg7k0x8g7pdsaa8ve9 + balance: [] + balance_waitgroup: 0 + delegated_balance: + amount: "0" + denom: uosmo + port_name: icacontroller-lstest-2.performance + redemption_rate: "1.000000000000000000" + validator_selection_allocation: [] + validators: [] + withdrawal_address: + address: osmo1xcs9r3ssmjndgr09jww29cs60ygck8g3udyl6savg3nkercfhl2qtp3lwv + balance: [] + balance_waitgroup: 0 + delegated_balance: + amount: "0" + denom: uosmo + port_name: icacontroller-lstest-2.withdrawal + withdrawal_waitgroup: 0 +``` + +### intent + +Query delegation intent for a given chain. + +`quicksilverd query interchainstaking intent [chain_id] [delegator_addr]` + +### deposit-account + +Query deposit account address for a given chain. + +`quicksilverd query interchainstaking deposit-account [chain_id]` + +## Keepers + +https://pkg.go.dev/github.com/ingenuity-build/quicksilver/x/interchainstaking/keeper + +## Parameters + +Module parameters: + +| Key | Type | Default | +|:-------------------------|:--------|:--------| +| deposit_interval | uint64 | 20 | +| validatorset_interval | uint64 | 200 | +| commission_rate | sdk.Dec | "0.025" | +| unbonding_enabled | bool | false | + + +Description of parameters: + +* `deposit_interval` - monitoring and handling interval of registered zones' deposit accounts; +* `validatorset_interval` - monitoring and updating interval of registered zones' validator sets; +* `commission_rate` - default commission rate for Quicksilver validators; +* `unbonding_enabled` - flag to indicate if unbondings are enabled for the Quicksilver protocol; + +## Begin Block + +Iterate through all registered zones and check validator set status. If the +status has changed, requery the validator set and update zone state. + +## After Epoch End + +The following is performed at the end of every epoch for each registered zone: + +* Aggregate Intents: + 1. Iterate through all stored instances of `DelegatorIntent` for each zone + and obtain the **delegator account balance**; + 2. Compute the **base balance** using the account balance and `RedpemtionRate`; + 3. Ordinalize the delegator's validator intents by `Weight`; + 4. Set the zone `AggregateIntent` and update zone state; +* Query delegator delegations for each zone and update delegation records: + 1. Query delegator delegations `cosmos.staking.v1beta1.Query/DelegatorDelegations`; + 2. For each response (per delegator `DelegationsCallback`), verify every + delegation record (via IBC `DelegationCallback`) and update delegation + record accordingly (add, update or remove); + 3. Update validator set; + 4. Update zone; +* Withdraw delegation rewards for each zone and distribute: + 1. Query delegator rewards `cosmos.distribution.v1beta1.Query/DelegationTotalRewards`; + 2. For each response (per delegator `RewardsCallback`), send withdrawal + messages for each of its validator delegations and add tally to + `WithdrawalWaitgroup`; + 3. For each IBC acknowledgement decrement the `WithdrawalWaitgroup`. Once + all responses are collected (`WithdrawalWaitgroup == 0`) query the balance + of `WithdrawalAddress` (`cosmos.bank.v1beta1.Query/AllBalances`), then + distribute rewards (`DistributeRewardsFromWithdrawAccount`). + + This approach ensures the exact rewards amount is known at the time of + distribution. + +## IBC + +### Messages, Acknowledgements & Handlers + +#### MsgWithdrawDelegatorReward + +Triggered at the end of every epoch if delegator accounts have accrued rewards. +Collects rewards to zone withdrawal account `WithdrawalAddress` and distributes +rewards once all delegator rewards withdrawals have been acknowledged. + +* **Endpoint:** `/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward` +* **Handler:** `HandleWithdrawRewards` + +#### MsgRedeemTokensforShares + +Triggered during execution of `Delegate` for delegation allocations that are +not in the zone `BaseDenom`. During callback the relevant delegation record is +updated. + +* **Endpoint:** `/cosmos.staking.v1beta1.MsgRedeemTokensforShares` +* **Handler:** `HandleRedeemTokens` + +#### MsgTokenizeShares + +Triggered by `RequestRedemption` when a user redeems qAssets. Withdrawal +records are set or updated accordingly. +See [MsgRequestRedemption](#MsgRequestRedemption). + +* **Endpoint:** `/cosmos.staking.v1beta1.MsgTokenizeShares` +* **Handler:** `HandleTokenizedShares` + +#### MsgDelegate + +Triggered by `Delegate` whenever delagtions are made by the protocol to zone +validators. `HandleDelegate` distinguishes `DelegationAddresses` and updates +delegation records for these delegation accounts. + +* **Endpoint:** `/cosmos.staking.v1beta1.MsgDelegate` +* **Handler:** `HandleDelegate` + +#### MsgBeginRedelegate + +Triggered at the end of every epoch during `Rebalance`. + +* **Endpoint:** `/cosmos.staking.v1beta1.MsgBeginRedelegate` +* **Handler:** `HandleBeginRedelegate` + +#### MsgSend + +Triggered by `TransferToDelegate` during `HandleReceiptTransaction`. +See [Deposit Interval](#Deposit-Interval). + +* **Endpoint:** `/cosmos.bank.v1beta1.MsgSend` +* **Handler:** `HandleCompleteSend` + +`HandleCompleteSend` executes one of the following options based on the +`FromAddress` and `ToAddress` of the msg: + +1. **Delegate rewards accoring to global intents.** + (If `FromAddress` is the zone's `WithdrawalAddress`); +2. **Withdraw native assets for user.** + (If `FromAddress` is one of zone's `DelegationAddresses`); +3. **Delegate amount according to delegation plan.** + (If `FromAddress` is `DepositAddress` and `ToAddress` is one of zone's `DelegationAddresses`); + +#### MsgMultiSend + +Not sent? + +* **Endpoint:** `/cosmos.bank.v1beta1.MsgMultiSend` +* **Handler:** `HandleCompleteMultiSend` + +#### MsgSetWithdrawAddress + +Triggered during zone initialization for every `DelegationAddresses` and +for the `PerformanceAddress`. The purpose of using a dedicated withdrawal +account allows for accurate rewards withdrawal accounting, that would otherwise +be impossible as the rewards amount will only be known at the time the msg is +triggered, and not at the time it was executed by the remote zone (due to network +latency and different zone block times, etc). + +* **Endpoint:** `/cosmos.distribution.v1beta1.MsgSetWithdrawAddress` +* **Handler:** `HandleUpdatedWithdrawAddress` + +#### MsgTransfer + +Triggered by `DistributeRewardsFromWithdrawAccount` to distribute rewards +across the zone delegation accounts and collect fees to the module fee account. +The `RedemptionRate` is updated accordingly. +See [WithdrawalAddress Balances](#WithdrawalAddress-Balances). + +* **Endpoint:** `/ibc.applications.transfer.v1.MsgTransfer` +* **Handler:** `HandleMsgTransfer` + +### Queries, Requests & Callbacks + +This module registeres the following queries, requests and callbacks. + +#### DepositAddress Balances + +For every registered zone a periodic `AllBalances` query is run against the +`DepositAddress`. The query is proven by utilizing provable KV queries that +update the individual account balances `AccountBalanceCallback`, trigger the +`depositInterval` and finally update the zone state. + +* **Query:** `cosmos.bank.v1beta1.Query/AllBalances` +* **Callback:** `AllBalancesCallback` + +#### Delegator Delegations + +Query delegator delegations for each zone and update delegation records. +See [After Epoch End](#After-Epoch-End). + +* **Query:** `cosmos.staking.v1beta1.Query/DelegatorDelegations` +* **Callback:** `DelegationsCallback` + +#### Delegate Total Rewards + +Withdraw delegation rewards for each zone and distribute. +See [After Epoch End](#After-Epoch-End). + +* **Query:** `cosmos.distribution.v1beta1.Query/DelegationTotalRewards` +* **Callback:** `RewardsCallback` + +#### WithdrawalAddress Balances + +Triggered by `HandleWithdrawRewards`. +See [MsgWithdrawDelegatorReward](#MsgWithdrawDelegatorReward). + +* **Query:** `cosmos.bank.v1beta1.Query/AllBalances` +* **Callback:** `DistributeRewardsFromWithdrawAccount` + +#### Deposit Interval + +Monitors transaction events of the zone `DepositAddress` on the remote chain +for receipt transactions that are then handled by `HandleReceiptTransaction`. +On valid receipts the delegation intent is updated (`UpdateIntent`) and new +qAssets minted and transferred to the sender (`MintQAsset`). A delegation +plan is computed (`DeterminePlanForDelegation`) and then executed +(`TransferToDelegate`). Successfully executed receipts are recorded to state. + +* **Query:** `cosmos.tx.v1beta1.Service/GetTxsEvent` +* **Callback:** `DepositIntervalCallback` + +#### Performance Balance Query + +Triggered at zone registration when the zone performance account +`PerformanceAddress` is created. It monitors the performance account balance +until sufficient funds are available to execute the performance delegations. +See [x/participationrewards/spec](../../participationrewards/spec/README.md). + +* **Query:** `cosmos.bank.v1beta1.Query/AllBalances` +* **Callback:** `PerfBalanceCallback` + +#### Validator Set Query + +An essential query to ensure that the registred zone state accurately reflects +the validator set of the remote zone for bonded, unbonded and unbonding +validators. + +* **Query:** `cosmos.staking.v1beta1.Query/Validators` +* **Callback:** `ValsetCallback` diff --git a/x/module/spec/README.md b/x/module/spec/README.md new file mode 100644 index 000000000..76ae2a404 --- /dev/null +++ b/x/module/spec/README.md @@ -0,0 +1,71 @@ +# Module Name + +## Abstract + +A brief module overview; + +## Contents + +1. [Concepts](#Concepts) +1. [State](#State) +1. [Messages](#Messages) +1. [Transactions](#Transactions) +1. [Events](#Events) +1. [Hooks](#Hooks) +1. [Queries](#Queries) +1. [Keepers](#Keepers) +1. [Parameters](#Parameters) +1. [Begin Block](#Begin-Block) +1. [End Block](#End-Block) + +## Concepts + +Key concepts, mechanisms and core logic for the module; + +## State + +Module specific state; + +## Messages + +Description of message types that trigger state transitions; + +## Transactions + +Description of transactions that collect messages in specific contexts to trigger state transitions; + +## Events + +Events emitted by module for tracking messages and index transactions; + +## Hooks + +Description of hook functions that may be used by other modules to execute operations at specific points within this module; + +## Queries + +Description of available information request queries; + +## Keepers + +Keepers exposed by module; + +## Parameters + +Module parameters: + +| Key | Type | Example | +|:-- ---|:-- ---|:-- ---| + +Description of parameters: + +* `param_name` - short description; + +## Begin Block + +Description of logic executed with optional methods or external hooks; + +## End Block + +Description of logic executed with optional methods or external hooks; + diff --git a/x/participationrewards/client/cli/tx.go b/x/participationrewards/client/cli/tx.go index ca9c1d0e2..bb1fa516a 100644 --- a/x/participationrewards/client/cli/tx.go +++ b/x/participationrewards/client/cli/tx.go @@ -83,7 +83,7 @@ func GetCmdAddProtocolDataProposal() *cobra.Command { cmd := &cobra.Command{ Use: "add-protocol-data [proposal-file]", Args: cobra.ExactArgs(1), - Short: "Submit a add protocol data proposal", + Short: "Submit an add protocol data proposal", Long: strings.TrimSpace( `Submit an add protocol data proposal along with an initial deposit. The proposal details must be supplied via a JSON file. diff --git a/x/participationrewards/keeper/submodule.go b/x/participationrewards/keeper/submodule.go index 153e1532d..9a85dafaa 100644 --- a/x/participationrewards/keeper/submodule.go +++ b/x/participationrewards/keeper/submodule.go @@ -6,6 +6,8 @@ import ( "github.com/ingenuity-build/quicksilver/x/participationrewards/types" ) +// Submodule defines the interface for for tracking off-chain qAssets with +// regards to participation rewards claims. type Submodule interface { Hooks(ctx sdk.Context, keeper Keeper) IsActive() bool diff --git a/x/participationrewards/spec/README.md b/x/participationrewards/spec/README.md new file mode 100644 index 000000000..42edb4b73 --- /dev/null +++ b/x/participationrewards/spec/README.md @@ -0,0 +1,423 @@ +# Participation Rewards + +## Abstract + +Module, `x/participatiorewards`, defines and implements the mechanisms to track, +allocate and distribute protocol participation rewards to users. + +## Contents + +1. [Concepts](#Concepts) +1. [State](#State) +1. [Messages](#Messages) +1. [Transactions](#Transactions) +1. [Proposals](#Proposals) +1. [Events](#Events) +1. [Hooks](#Hooks) +1. [Queries](#Queries) +1. [Keepers](#Keepers) +1. [Parameters](#Parameters) +1. [Begin Block](#Begin-Block) +1. [End Block](#End-Block) +1. [After Epoch End](#After-Epoch-End) + +## Concepts + +The purpose of the participation rewards module is to reward users for protocol participation. + +Specifically, we want to reward users for: + +1. Staking and locking of QCK on the Quicksilver chain; +2. Positive validator selection, validators are ranked equally on performance and decentralization; +3. Holdings of off-chain assets (qAssets); + +The total inflation allocation for participation rewards is divided +proportionally for each of the above according to the module [parameters](#Parameters). + +### 1. Lockup Rewards + +The staking and lockup rewards allocation is moved to the fee collector account +to be distributed by the staking module on the next begin blocker. Thus, the +**user rewards allocation** will be proportional to their stake of the overall +staked pool. + +### 2. Validator Selection Rewards + +Validators are ranked on two aspects with equal weighting, namely +decentralization and performance. + +The **decentralilzation scores** are based on the normalized voting power of the +validators within a given zone, favouring smaller validators. + +The **performance scores** are based on the validator rewards earned by a +special performance account that delegates an exact amount to each validator. +The total rewards earned by the performance account is divided by the number of +active validators to obtain the expected rewards. The performance score for +each validator is then simply the percentage of actual rewards compared to the +expected rewards (capped at 100%). + +The overall **validator scores** are simply the multiple of their +decentralization score and their performance score. + +Individual **users scores** are based on their validator selection intent +signalled at the previous epoch boundary. The user intent weights are +multiplied by the corresponding validator scores for the given zone and an +overall user score is calculated for the given zone along with an +**overall zone score**. + +Each zone receives a **proportional rewards allocation** based on the total +value locked (TVL) for the zone relative to the TVL of all zones across the +protocol. + +The overall zone score and the proportional rewards allocation determines the +amount of **tokens per point (TPP)** to be allocated for the given zone. Thus, +the **user rewards allocation** is the user's score multiplied by the TPP. + +### 3. Holdings Rewards + +Each zone receives a **proportional rewards allocation** based on the total +value locked (TVL) for the zone relative to the TVL of all zones across the +protocol. + +Thus, the **user rewards allocation** is proportional to their holdings of +qAssets across all zones, capped at 2% per account. + +### 4. Protocol Data + +Rewarding for protocol participation, specifically for off-chain assets, +requires provable claims. **Protocol Data** describes any protocol specific +state that must be tracked in order to obtain provable claims. The module +defines a `Submodule` interface that must be implemented to obtain provable +claims against any specific protocol. + +The following standrad sub-modules are implemented: + +* `LiquidTokenModule` - to track off-chain liquid qAssets. +* `OsmosisModule` - to track qAssets locked in Osmosis pools. + +## State + +A `Score` is maintained for every `Validator` within a `Zone`. `Score` is +initially set to zero and is updated at the end of every epoch to reflect the +**overall score** for the validator (decntralization_score * performance_score). + +A `ValidatorSelectionAllocation` and `HoldingsAllocation` are maintained for +every `Zone`. These are calculated and set at the end of every epoch according +to the rewards allocation proportions that are distributed to zones based on +their Total Value Locked (TVL) relative to the TVL of the overall protocol. + +### ProtocolData + +#### Types + +```go +type ProtocolDataType int32 + +const ( + // Undefined action (per protobuf spec) + ProtocolDataTypeUndefined ProtocolDataType = 0 + ProtocolDataTypeConnection ProtocolDataType = 1 + ProtocolDataTypeOsmosisParams ProtocolDataType = 2 + ProtocolDataTypeLiquidToken ProtocolDataType = 3 + ProtocolDataTypeOsmosisPool ProtocolDataType = 4 + ProtocolDataTypeCrescentPool ProtocolDataType = 5 + ProtocolDataTypeSifchainPool ProtocolDataType = 6 +) + +var ProtocolDataType_name = map[int32]string{ + 0: "ProtocolDataTypeUndefined", + 1: "ProtocolDataTypeConnection", + 2: "ProtocolDataTypeOsmosisParams", + 3: "ProtocolDataTypeLiquidToken", + 4: "ProtocolDataTypeOsmosisPool", + 5: "ProtocolDataTypeCrescentPool", + 6: "ProtocolDataTypeSifchainPool", +} + +var ProtocolDataType_value = map[string]int32{ + "ProtocolDataTypeUndefined": 0, + "ProtocolDataTypeConnection": 1, + "ProtocolDataTypeOsmosisParams": 2, + "ProtocolDataTypeLiquidToken": 3, + "ProtocolDataTypeOsmosisPool": 4, + "ProtocolDataTypeCrescentPool": 5, + "ProtocolDataTypeSifchainPool": 6, +} +``` + +#### Connection + +```go +// ConnectionProtocolData defines state for connection tracking. +type ConnectionProtocolData struct { + ConnectionID string + ChainID string + LastEpoch int64 + Prefix string +} +``` + +#### Liquid + +```go +// LiquidAllowedDenomProtocolData defines protocol state to track off-chain +// liquid qAssets. +type LiquidAllowedDenomProtocolData struct { + // The chain on which the qAssets reside currently. + ChainID string + // The chain for which the qAssets were issued. + RegisteredZoneChainID string + // The IBC denom. + IbcDenom string + // The qAsset denom. + QAssetDenom string +} +``` + +#### Osmosis + +```go +// OsmosisPoolProtocolData defines protocol state to track qAssets locked in +// Osmosis pools. +type OsmosisPoolProtocolData struct { + PoolID uint64 + PoolName string + LastUpdated time.Time + PoolData json.RawMessage + PoolType string + Zones map[string]string // chainID: IBC/denom +} + +type OsmosisParamsProtocolData struct { + ChainID string +} +``` + +## Messages + +Description of message types that trigger state transitions; + +```protobuf +// Msg defines the participationrewards Msg service. +service Msg { + rpc SubmitClaim(MsgSubmitClaim) returns (MsgSubmitClaimResponse) { + option (google.api.http) = { + post : "/quicksilver/tx/v1/participationrewards/claim" + body : "*" + }; + }; +} +``` + +### MsgSubmitClaim + +SubmitClaim is used to verify, by proof, that the given user address has a claim against the given asset type for the given zone. + +```go +// MsgSubmitClaim represents a message type for submitting a participation +// claim regarding the given zone (chain). +type MsgSubmitClaim struct { + UserAddress string `protobuf:"bytes,1,opt,name=user_address,proto3" json:"user_address,omitempty"` + Zone string `protobuf:"bytes,2,opt,name=zone,proto3" json:"zone,omitempty"` + SrcZone string `protobuf:"bytes,3,opt,name=src_zone,proto3" json:"src_zone,omitempty"` + ClaimType types.ClaimType `protobuf:"varint,4,opt,name=claim_type,proto3,enum=quicksilver.claimsmanager.v1.ClaimType" json:"claim_type,omitempty"` + Proofs []*types.Proof `protobuf:"bytes,5,rep,name=proofs,proto3" json:"proofs,omitempty"` +} +``` + +* **UserAddress** - the address of the claimant account on the native chain; +* **Zone** - the native zone related to the qAsset; +* **SrcZone** - the zone on which the qAsset is used (from where the proof originates); +* **ClaimType** - see [`x/claimsmanager/spec/README.md#ClaimType`](../../claimsmanager/spec/README.md#ClaimType); +* **Proofs** - see [`x/claimsmanager/spec/README.md#Proof`](../../claimsmanager/spec/README.md#Proof); + +**Transaction**: [`claim`](#claim) + +## Transactions + +Description of transactions that collect messages in specific contexts to trigger state transitions; + +### claim + +Submit proof of assets held in the given zone. + +`claim [zone] [src-zone] [claim-type] [payload-file].json` + +## Proposals + +### add-protocol-data + +Submit an add protocol data proposal along with an initial deposit. +The proposal details must be supplied via a JSON file. + +`add-protocol-data [proposal-file]` + +Example: + +`quicksilverd tx gov submit-proposal add-protocol-data --from=` + +Where proposal.json contains: + +```json +{ + "title": "Add Osmosis Atom/qAtom Pool", + "description": "Add Osmosis Atom/qAtom Pool to support participation rewards", + "protocol": "osmosis", + "key": "pools/XXX", + "type": "osmosispool", + "data": { + "poolID": "596", + "ibcToken": "27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2", + "localDenom": "uqatom" + }, + "deposit": "512000000uqck" +} +``` + +## Events + +N/A + +[? this should probably emit some events for monitoring and tracking purposes ?] + +## Hooks + +N/A + +## Queries + +Participation Rewards module provides the below queries to check the module's state: + +```protobuf +// Query provides defines the gRPC querier service. +service Query { + // Params returns the total set of participation rewards parameters. + rpc Params(QueryParamsRequest) returns (QueryParamsResponse) { + option (google.api.http).get = + "/quicksilver/participationrewards/v1/params"; + } + + rpc ProtocolData(QueryProtocolDataRequest) + returns (QueryProtocolDataResponse) { + option (google.api.http).get = + "/quicksilver/participationrewards/v1/protocoldata/{type}/{key}"; + } +} +``` + +### params + +Query the current airdrop module parameters. + +```go +// QueryParamsRequest is the request type for the Query/Params RPC method. +type QueryParamsRequest struct { +} + +// QueryParamsResponse is the response type for the Query/Params RPC method. +type QueryParamsResponse struct { + // params defines the parameters of the module. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} +``` + +### protocoldata + +Query the specified protocol data. + +```go +// QueryProtocolDataRequest is the request type for querying Protocol Data. +type QueryProtocolDataRequest struct { + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` +} + +// QueryProtocolDataResponse is the response type for querying Protocol Data. +type QueryProtocolDataResponse struct { + // data defines the underlying protocol data. + Data []encoding_json.RawMessage `protobuf:"bytes,1,rep,name=data,proto3,casttype=encoding/json.RawMessage" json:"data,omitempty" yaml:"data"` +} +``` + +## Keepers + +https://pkg.go.dev/github.com/ingenuity-build/quicksilver/x/participationrewards/keeper + +## Parameters + +Module parameters: + +| Key | Type | Example | +|:--------------------------------------------------------|:-------------|:--------| +| distribution_proportions.validator_selection_allocation | string (dec) | "0.34" | +| distribution_proportions.holdings_allocation | string (dec) | "0.33" | +| distribution_proportions.lockup_allocation | string (dec) | "0.33" | + +Description of parameters: + +* `validator_selection_allocation` - the percentage of inflation rewards allocated to validator selection rewards; +* `holdings_allocation` - the percentage of inflation rewards allocated to qAssets hoildings rewards; +* `lockup_allocation` - the percentage of inflation rewards allocated to staking and locking of QCK; + +## Begin Block + +N/A + +## End Block + +N/A + +## After Epoch End + +The following is performed at the end of every epoch: + +* Obtains the rewards allocations according to the module balances and + distribution proportions parameters; +* Allocate zone rewards according to the proportional zone Total Value Locked + (TVL) for both **Validator Selection** and **qAsset Holdings**; +* Calculate validator selection scores and allocations for every zone: + 1. Obtain performance account delegation rewards (`performanceScores`); + 2. Calculate decentralization scores (`distributionScores`); + 3. Calculate overall validator scores; + 4. Calculate user validator selection rewards; + 5. Distribute validator selection rewards; +* Calculate qAsset holdings: + 1. Obtain qAssets held by account (locally and off-chain via claims / Poof of + Posession); + 2. Calculate user proportion (cap at 2%); + 3. Normalize and distribute allocation; +* Allocate lockup rewards by sending portion to `feeCollector` for distribution + by Staking Module; +* Update protocol data with the epoch boundary block height; +* Update osmosis pools protocol data; + +## IBC + +### Messages, Acknowledgements & Handlers + +### Queries, Requests & Callbacks + +This module registeres the following queries, requests and callbacks. + +#### Performance Delegation Rewards + +Queries the performance delegation rewards of the zone and computes the +validator scores based on the performance rewards. + +* **Query:** `cosmos.distribution.v1beta1.Query/DelegationTotalRewards` +* **Callback:** `ValidatorSelectionRewardsCallback` + +#### Osmosis Pool Update + +Updates the registered Osmosis pools at the end of each epoch. + +* **Query:** `store/gamm/key` +* **Callback:** `OsmosisPoolUpdateCallback` + +#### Epoch Block + +Queries and records the block height of the registered zone at the epoch +boundary. + +* **Query:** `cosmos.base.tendermint.v1beta1.Service/GetLatestBlock` +* **Callback:** `SetEpochBlockCallback` diff --git a/x/participationrewards/types/protocol_data.go b/x/participationrewards/types/protocol_data.go index 4c08a4d3c..42250eb59 100644 --- a/x/participationrewards/types/protocol_data.go +++ b/x/participationrewards/types/protocol_data.go @@ -73,6 +73,7 @@ type ProtocolDataI interface { ValidateBasic() error } +// ConnectionProtocolData defines state for connection tracking. type ConnectionProtocolData struct { ConnectionID string ChainID string diff --git a/x/participationrewards/types/submodule_liquid.go b/x/participationrewards/types/submodule_liquid.go index fc50bde0a..8766845cf 100644 --- a/x/participationrewards/types/submodule_liquid.go +++ b/x/participationrewards/types/submodule_liquid.go @@ -6,11 +6,17 @@ import ( "github.com/ingenuity-build/quicksilver/internal/multierror" ) +// LiquidAllowedDenomProtocolData defines protocol state to track off-chain +// liquid qAssets. type LiquidAllowedDenomProtocolData struct { - ChainID string + // The chain on which the qAssets reside currently. + ChainID string + // The chain for which the qAssets were issued. RegisteredZoneChainID string - IbcDenom string - QAssetDenom string + // The IBC denom. + IbcDenom string + // The qAsset denom. + QAssetDenom string } func (lpd LiquidAllowedDenomProtocolData) ValidateBasic() error { diff --git a/x/participationrewards/types/submodule_osmosis.go b/x/participationrewards/types/submodule_osmosis.go index dffa48c3a..d84d4c1bd 100644 --- a/x/participationrewards/types/submodule_osmosis.go +++ b/x/participationrewards/types/submodule_osmosis.go @@ -11,6 +11,8 @@ import ( "github.com/ingenuity-build/quicksilver/osmosis-types/gamm/pool-models/stableswap" ) +// OsmosisPoolProtocolData defines protocol state to track qAssets locked in +// Osmosis pools. type OsmosisPoolProtocolData struct { PoolID uint64 PoolName string