Skip to content

Commit

Permalink
add new claim queries (#679)
Browse files Browse the repository at this point in the history
Closes: #XXX

## Context and purpose of the change

This PR adds two new queries to make FE development easier.

* (k Keeper) GetClaimMetadata(ctx sdk.Context) ([]types.ClaimMetadata, error): Returns a list of claim metadata for all airdrops. It retrieves all airdrop identifiers and the associated epoch information to determine the current round, current round start time, and current round end time for each airdrop.
* (k Keeper) GetClaimStatus(ctx sdk.Context, addr sdk.AccAddress) ([]types.ClaimStatus, error): Returns a list of claim statuses associated with the given user account address. It retrieves all airdrop identifiers and checks for each airdrop whether the user has claimed it or not. It filters claims to those a user has a claim record for, if a user has no claim record for an airdrop, nothing is returned.

As well as two helper functions 
* areAllTrue(bools []bool) bool: This function takes a slice of boolean values and checks if all of the values are true. If all values are true, the function returns true. Otherwise, it returns false.
* currentAirdropRound(start time.Time) int: This function calculates and returns the current airdrop round number based on the start time. It uses an initial round duration of 90 days and subsequent round durations of 30 days.


## Example query
```
➜  build git:(query-claim-status-by-address) ✗ ./strided q claim claim-metadata --node http://localhost:26657
claim_metadata:
- airdrop_identifier: stride
  current_round: "1"
  current_round_end: "2023-04-23T00:00:00Z"
  current_round_start: "2023-03-23T00:00:00Z"
- airdrop_identifier: gaia
  current_round: "1"
  current_round_end: "2023-04-23T00:00:00Z"
  current_round_start: "2023-03-23T00:00:00Z"
- airdrop_identifier: osmosis
  current_round: "1"
  current_round_end: "2023-04-23T00:00:00Z"
  current_round_start: "2023-03-23T00:00:00Z"
- airdrop_identifier: juno
  current_round: "1"
  current_round_end: "2023-04-23T00:00:00Z"
  current_round_start: "2023-03-23T00:00:00Z"
- airdrop_identifier: stars
  current_round: "1"
  current_round_end: "2023-04-23T00:00:00Z"
  current_round_start: "2023-03-23T00:00:00Z"
➜  build git:(query-claim-status-by-address) ✗ ./strided q claim claim-status stride1x2227f8js7c2eag5yy97x8tk2unchhrjp33mrs --node http://localhost:26657
claim_status:
- airdrop_identifier: stride
  claimed: true
- airdrop_identifier: gaia
- airdrop_identifier: osmosis
  claimed: true
- airdrop_identifier: juno
- airdrop_identifier: stars
  claimed: true
```


## Brief Changelog




## Author's Checklist

I have...

- [x] Run and PASSED locally all GAIA integration tests
- [ ] If the change is contentful, I either:
    - [ ] Added a new unit test OR 
    - [ ] Added test cases to existing unit tests
- [ ] OR this change is a trivial rework / code cleanup without any test coverage

If skipped any of the tests above, explain.


## Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] manually tested (if applicable)
- [ ] confirmed the author wrote unit tests for new logic
- [ ] reviewed documentation exists and is accurate


## Documentation and Release Note

  - [ ] Does this pull request introduce a new feature or user-facing behavior changes? 
  - [ ] Is a relevant changelog entry added to the `Unreleased` section in `CHANGELOG.md`?
  - [ ] This pull request updates existing proto field values (and require a backend and frontend migration)? 
  - [ ] Does this pull request change existing proto field names (and require a frontend migration)?
  How is the feature or change documented? 
      - [ ] not applicable
      - [ ] jira ticket `XXX` 
      - [ ] specification (`x/<module>/spec/`) 
      - [ ] README.md 
      - [ ] not documented
  • Loading branch information
asalzmann committed Mar 24, 2023
1 parent 75f6b6b commit 7e7335a
Show file tree
Hide file tree
Showing 8 changed files with 1,984 additions and 98 deletions.
45 changes: 45 additions & 0 deletions proto/stride/claim/query.proto
Expand Up @@ -7,6 +7,8 @@ import "cosmos/base/v1beta1/coin.proto";
import "stride/claim/claim.proto";
import "stride/claim/params.proto";
import "stride/vesting/vesting.proto";
import "google/protobuf/timestamp.proto";


option go_package = "github.com/Stride-Labs/stride/v7/x/claim/types";

Expand Down Expand Up @@ -35,6 +37,49 @@ service Query {
returns (QueryUserVestingsResponse) {
option (google.api.http).get = "/claim/user_vestings/{address}";
}
rpc ClaimStatus(QueryClaimStatusRequest) returns (QueryClaimStatusResponse) {
option (google.api.http).get = "/claim/claim_status/{address}";
}
rpc ClaimMetadata(QueryClaimMetadataRequest)
returns (QueryClaimMetadataResponse) {
option (google.api.http).get = "/claim/claim_metadata";
}
}

message ClaimStatus {
string airdrop_identifier = 1;
bool claimed = 2;
}
message QueryClaimStatusRequest {
string address = 1 [ (gogoproto.moretags) = "yaml:\"address\"" ];
}
message QueryClaimStatusResponse {
repeated ClaimStatus claim_status = 1 [
(gogoproto.moretags) = "yaml:\"claim_status\"",
(gogoproto.nullable) = false
];
}

message ClaimMetadata {
string airdrop_identifier = 1;
string current_round = 2;
google.protobuf.Timestamp current_round_start = 3 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"current_round_start\""
];
google.protobuf.Timestamp current_round_end = 4 [
(gogoproto.stdtime) = true,
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"current_round_end\""
];
}
message QueryClaimMetadataRequest {}
message QueryClaimMetadataResponse {
repeated ClaimMetadata claim_metadata = 1 [
(gogoproto.moretags) = "yaml:\"claim_metadata\"",
(gogoproto.nullable) = false
];
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand Down
28 changes: 28 additions & 0 deletions x/claim/README.md
Expand Up @@ -181,6 +181,8 @@ service Query {
rpc ClaimRecord(QueryClaimRecordRequest) returns (QueryClaimRecordResponse) {}
rpc ClaimableForAction(QueryClaimableForActionRequest) returns (QueryClaimableForActionResponse) {}
rpc TotalClaimable(QueryTotalClaimableRequest) returns (QueryTotalClaimableResponse) {}
rpc ClaimStatus(QueryClaimStatusRequest) returns (QueryClaimStatusResponse) {}
rpc ClaimMetadata(QueryClaimMetadataRequest) returns (QueryClaimMetadataResponse) {}
}
```

Expand All @@ -207,6 +209,32 @@ Query the total claimable amount that would be earned if all remaining actions w
strided query claim total-claimable $(strided keys show -a {your key name}) ActionAddLiquidity
```

Query claim status, across all claims, for an address. Returns a list of `ClaimStatus` structs.
```
message ClaimStatus {
string airdrop_identifier = 1;
bool claimed = 2;
}
```

```sh
strided query claim claim-status $(strided keys show -a {your key name})
```

Query claim metadata, across all claims. Returns a `ClaimMetadata` struct, which contains data about the status of each claim.
```
message ClaimMetadata {
string airdrop_identifier = 1;
string current_round = 2;
google.protobuf.Timestamp current_round_start = 3;
google.protobuf.Timestamp current_round_end = 4;
}
```

```sh
strided query claim claim-metadata
```

## Events

`claim` module emits the following events at the time of hooks:
Expand Down
65 changes: 65 additions & 0 deletions x/claim/client/cli/query.go
Expand Up @@ -30,6 +30,8 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
GetCmdQueryClaimableForAction(),
GetCmdQueryTotalClaimable(),
GetCmdQueryUserVestings(),
GetCmdClaimStatus(),
GetCmdQueryClaimMetadata(),
)

return claimQueryCmd
Expand Down Expand Up @@ -246,3 +248,66 @@ $ %s query claim user-vestings stride1h4astdfzjhcwahtfrh24qtvndzzh49xvqtfftk
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

// GetCmdClaimStatus implements the query user vestings command.
func GetCmdClaimStatus() *cobra.Command {
cmd := &cobra.Command{
Use: "claim-status [address]",
Args: cobra.ExactArgs(1),
Short: "Query claim status for an address, across all claims.",
Long: strings.TrimSpace(
fmt.Sprintf(`Query claim status for an address, across all claims.
Example:
$ %s query claim claim-status stride1h4astdfzjhcwahtfrh24qtvndzzh49xvqtfftk
`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)
// Query store
res, err := queryClient.ClaimStatus(context.Background(), &types.QueryClaimStatusRequest{
Address: args[0],
})
if err != nil {
return err
}
return clientCtx.PrintObjectLegacy(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

// GetCmdQueryClaimMetadata implements the query user vestings command.
func GetCmdQueryClaimMetadata() *cobra.Command {
cmd := &cobra.Command{
Use: "claim-metadata",
Args: cobra.ExactArgs(0),
Short: "Query claim metadata.",
Long: strings.TrimSpace(
fmt.Sprintf(`Query claim-metadata. Returns the current round, start and end time for claims. %s`,
version.AppName,
),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)
// Query store
res, err := queryClient.ClaimMetadata(context.Background(), &types.QueryClaimMetadataRequest{})
if err != nil {
return err
}
return clientCtx.PrintObjectLegacy(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
118 changes: 118 additions & 0 deletions x/claim/keeper/claim.go
Expand Up @@ -146,6 +146,23 @@ func (k Keeper) GetAirdropByIdentifier(ctx sdk.Context, airdropIdentifier string
return nil
}

func (k Keeper) GetAirdropIds(ctx sdk.Context) []string {
params, err := k.GetParams(ctx)
if err != nil {
panic(err)
}

// init airdrop ids
airdropIds := []string{}

for _, airdrop := range params.Airdrops {
// append airdrop to airdrop ids
airdropIds = append(airdropIds, airdrop.AirdropIdentifier)
}

return airdropIds
}

// GetDistributorAccountBalance gets the airdrop coin balance of module account
func (k Keeper) GetDistributorAccountBalance(ctx sdk.Context, airdropIdentifier string) (sdk.Coin, error) {
airdrop := k.GetAirdropByIdentifier(ctx, airdropIdentifier)
Expand Down Expand Up @@ -422,6 +439,107 @@ func (k Keeper) GetUserVestings(ctx sdk.Context, addr sdk.AccAddress) (vestingty
}
}

func AreAllTrue(bools []bool) bool {
for _, b := range bools {
if !b {
return false
}
}
return true
}

// GetClaimStatus returns all claim status associated with the user account
func (k Keeper) GetClaimStatus(ctx sdk.Context, addr sdk.AccAddress) ([]types.ClaimStatus, error) {
// Get all airdrop identifiers
airdropIdentifiers := k.GetAirdropIds(ctx)
var claimStatusList []types.ClaimStatus
for _, airdropId := range airdropIdentifiers {

// Get the claim record for a user, airdrop pair
claimRecord, err := k.GetClaimRecord(ctx, addr, airdropId)
if err != nil {
return nil, err
}
if claimRecord.Address == "" {
// if there's no claim record, the user is not eligible
// for this airdrop, so skip it
continue
}

// If all actions are completed, the user has claimed
claimed := AreAllTrue(claimRecord.ActionCompleted)
claimStatus := types.ClaimStatus{
AirdropIdentifier: airdropId,
Claimed: claimed,
}
claimStatusList = append(claimStatusList, claimStatus)
}

return claimStatusList, nil
}

func CurrentAirdropRound(start time.Time) int {
// Define constants for 90 days and 30 days
const initialRoundDuration = 90 * 24 * time.Hour
const subsequentRoundDuration = 30 * 24 * time.Hour

// Calculate the time passed since the start
timePassed := time.Since(start)

// Check if the initial round is still ongoing
if timePassed < initialRoundDuration {
return 1
}

// Calculate the time passed after the initial round
timePassedAfterInitialRound := timePassed - initialRoundDuration

// Calculate the number of subsequent rounds passed
subsequentRoundsPassed := timePassedAfterInitialRound / subsequentRoundDuration

// Add 1 for the initial round and 1 for the current round
return 1 + 1 + int(subsequentRoundsPassed)
}

// GetClaimMetadata returns all claim status associated with the user account
func (k Keeper) GetClaimMetadata(ctx sdk.Context) []types.ClaimMetadata {
var claimMetadataList []types.ClaimMetadata

airdropIdentifiers := k.GetAirdropIds(ctx)
epochs := k.epochsKeeper.AllEpochInfos(ctx)

for _, airdropId := range airdropIdentifiers {
// loop over epochs to match epochs to airdrop identifier
var currentRoundStart time.Time
var currentRoundEnd time.Time
var absoluteStartTime time.Time
var duration time.Duration
for _, epoch := range epochs {
epochIdentifier := strings.TrimPrefix(epoch.Identifier, "airdrop-")
if epochIdentifier == airdropId {
// found the epoch for this airdrop
currentRoundStart = epoch.CurrentEpochStartTime
absoluteStartTime = epoch.StartTime
duration = epoch.Duration
}
}

currentRoundEnd = currentRoundStart.Add(duration)
currentRound := strconv.Itoa(CurrentAirdropRound(absoluteStartTime))

claimMetadata := types.ClaimMetadata{
AirdropIdentifier: airdropId,
CurrentRound: currentRound,
CurrentRoundStart: currentRoundStart,
CurrentRoundEnd: currentRoundEnd,
}

claimMetadataList = append(claimMetadataList, claimMetadata)
}

return claimMetadataList
}

// GetClaimable returns claimable amount for a specific action done by an address
func (k Keeper) GetUserTotalClaimable(ctx sdk.Context, addr sdk.AccAddress, airdropIdentifier string, includeClaimed bool) (sdk.Coins, error) {
claimRecord, err := k.GetClaimRecord(ctx, addr, airdropIdentifier)
Expand Down

0 comments on commit 7e7335a

Please sign in to comment.