Skip to content

Commit

Permalink
Query redemption records for multiple addresses (#1075)
Browse files Browse the repository at this point in the history
Co-authored-by: vish-stride <vishal@stridelabs.co>
  • Loading branch information
ethan-stride and shellvish authored Jan 15, 2024
1 parent 121f2ac commit 910b667
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 4 deletions.
20 changes: 16 additions & 4 deletions x/stakeibc/keeper/grpc_query_address_unbondings.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ import (
const nanosecondsInDay = 86400000000000

func (k Keeper) AddressUnbondings(c context.Context, req *types.QueryAddressUnbondings) (*types.QueryAddressUnbondingsResponse, error) {
// The function queries all the unbondings associated with a Stride address.
// The function queries all the unbondings associated with Stride addresses.
// This should provide more visiblity into the unbonding process for a user.

if req == nil || req.Address == "" {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
ctx := sdk.UnwrapSDKContext(c)

var addressUnbondings []types.AddressUnbonding
// The address field can either be a single address or several comma separated
addresses := strings.Split(req.Address, ",")

addressUnbondings := []types.AddressUnbonding{}

// get the relevant day
dayEpochTracker, found := k.GetEpochTracker(ctx, epochtypes.DAY_EPOCH)
Expand All @@ -47,7 +50,16 @@ func (k Keeper) AddressUnbondings(c context.Context, req *types.QueryAddressUnbo
continue
}
userRedemptionRecordAddress := userRedemptionRecordComponents[2]
if userRedemptionRecordAddress == req.Address {

// Check if the userRedemptionRecordAddress is one targeted by the address(es) in the query
targetAddress := false
for _, address := range addresses {
if userRedemptionRecordAddress == strings.TrimSpace(address) {
targetAddress = true
break
}
}
if targetAddress {
userRedemptionRecord, found := k.RecordsKeeper.GetUserRedemptionRecord(ctx, userRedemptionRecordId)
if !found {
continue // the record has already been claimed
Expand All @@ -70,7 +82,7 @@ func (k Keeper) AddressUnbondings(c context.Context, req *types.QueryAddressUnbo
unbondingTimeStr := time.Unix(0, int64(unbondingTime)).UTC().String()

addressUnbonding := types.AddressUnbonding{
Address: req.Address,
Address: userRedemptionRecordAddress,
Receiver: userRedemptionRecord.Receiver,
UnbondingEstimatedTime: unbondingTimeStr,
Amount: userRedemptionRecord.NativeTokenAmount,
Expand Down
270 changes: 270 additions & 0 deletions x/stakeibc/keeper/grpc_query_address_unbondings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

keepertest "github.com/Stride-Labs/stride/v17/testutil/keeper"
epochtypes "github.com/Stride-Labs/stride/v17/x/epochs/types"
recordtypes "github.com/Stride-Labs/stride/v17/x/records/types"
"github.com/Stride-Labs/stride/v17/x/stakeibc/types"
)

func TestAddressUnbondings(t *testing.T) {
keeper, ctx := keepertest.StakeibcKeeper(t)
wctx := sdk.WrapSDKContext(ctx)

// Setup DayEpoch Tracker for current epoch 100
const nanosecondsInDay = 86400000000000
const testTimeNanos = 1704067200000000000 // 2024-01-01 00:00:00 is start of epoch 100
dayEpochTracker := types.EpochTracker{
EpochIdentifier: epochtypes.DAY_EPOCH,
EpochNumber: 100,
NextEpochStartTime: testTimeNanos + nanosecondsInDay,
Duration: nanosecondsInDay,
}
keeper.SetEpochTracker(ctx, dayEpochTracker)

// Setup HostZones with different unbonding periods
cosmosZone := types.HostZone{
ChainId: "cosmos",
UnbondingPeriod: 21,
}
keeper.SetHostZone(ctx, cosmosZone)
strideZone := types.HostZone{
ChainId: "stride",
UnbondingPeriod: 9, // just so different unbonding period
}
keeper.SetHostZone(ctx, strideZone)

// Setup some epoch unbonding records to test against
for _, epochUnbondingRecord := range []*recordtypes.EpochUnbondingRecord{
{
EpochNumber: 101,
HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{
{
HostZoneId: "stride",
UserRedemptionRecords: []string{
"stride.101.strideAddrUserA",
"stride.101.strideAddrUserB",
},
},
{
HostZoneId: "cosmos",
UserRedemptionRecords: []string{
"cosmos.101.cosmosAddrUserA",
},
},
},
},
{
EpochNumber: 110,
HostZoneUnbondings: []*recordtypes.HostZoneUnbonding{
{
HostZoneId: "stride",
UserRedemptionRecords: []string{
"stride.110.strideAddrUserA",
},
},
},
},
} {
keeper.RecordsKeeper.SetEpochUnbondingRecord(ctx, *epochUnbondingRecord)
}

// Setup corresponding user unbonding records to test with
for _, userRedemptionRecord := range []*recordtypes.UserRedemptionRecord{
{
Id: "stride.101.strideAddrUserA",
Receiver: "strideAddrUserA",
NativeTokenAmount: sdk.NewInt(2000),
Denom: "ustrd",
HostZoneId: "stride",
EpochNumber: uint64(101),
ClaimIsPending: false,
},
{
Id: "stride.110.strideAddrUserA",
Receiver: "strideAddrUserA",
NativeTokenAmount: sdk.NewInt(5000),
Denom: "ustrd",
HostZoneId: "stride",
EpochNumber: uint64(110),
ClaimIsPending: false,
},
{
Id: "stride.101.strideAddrUserB",
Receiver: "strideAddrUserB",
NativeTokenAmount: sdk.NewInt(8500),
Denom: "ustrd",
HostZoneId: "stride",
EpochNumber: uint64(101),
ClaimIsPending: false,
},
{
Id: "cosmos.101.cosmosAddrUserA",
Receiver: "cosmosAddrUserA",
NativeTokenAmount: sdk.NewInt(1200),
Denom: "uatom",
HostZoneId: "cosmos",
EpochNumber: uint64(101),
ClaimIsPending: false,
},
} {
keeper.RecordsKeeper.SetUserRedemptionRecord(ctx, *userRedemptionRecord)
}

// Test cases:
// Single Address --> no redemption records found
// Single Address --> single redemption record found
// Single Address --> multiple redemption records across different epochs
// Multiple Addresses --> find record for only one address but not the others
// Multiple Addresses --> find user records for each, more than one for some
// Invalid query or address --> expected err case

for _, tc := range []struct {
desc string
request *types.QueryAddressUnbondings
response *types.QueryAddressUnbondingsResponse
err error
}{
{
desc: "Single input address without any records expected",
request: &types.QueryAddressUnbondings{
Address: "cosmosAddrUserB",
},
response: &types.QueryAddressUnbondingsResponse{
AddressUnbondings: []types.AddressUnbonding{
},
},
},
{
desc: "Single input address with one record expected",
request: &types.QueryAddressUnbondings{
Address: "cosmosAddrUserA",
},
response: &types.QueryAddressUnbondingsResponse{
AddressUnbondings: []types.AddressUnbonding{
{
Address: "cosmosAddrUserA",
Receiver: "cosmosAddrUserA",
Amount: sdk.NewInt(1200),
Denom: "uatom",
EpochNumber: uint64(101),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-27 00:00:00 +0000 UTC",
},
},
},
},
{
desc: "Single input address with multiple records across epochs",
request: &types.QueryAddressUnbondings{
Address: "strideAddrUserA",
},
response: &types.QueryAddressUnbondingsResponse{
AddressUnbondings: []types.AddressUnbonding{
{
Address: "strideAddrUserA",
Receiver: "strideAddrUserA",
Amount: sdk.NewInt(2000),
Denom: "ustrd",
EpochNumber: uint64(101),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-11 00:00:00 +0000 UTC",
},
{
Address: "strideAddrUserA",
Receiver: "strideAddrUserA",
Amount: sdk.NewInt(5000),
Denom: "ustrd",
EpochNumber: uint64(110),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-11 00:00:00 +0000 UTC",
},
},
},
},
{
desc: "Multiple input addresses only one has a record, others unfound",
request: &types.QueryAddressUnbondings{
Address: "cosmosAddrUserB,strideAddrUserB,strideAddrUserC",
},
response: &types.QueryAddressUnbondingsResponse{
AddressUnbondings: []types.AddressUnbonding{
{
Address: "strideAddrUserB",
Receiver: "strideAddrUserB",
Amount: sdk.NewInt(8500),
Denom: "ustrd",
EpochNumber: uint64(101),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-11 00:00:00 +0000 UTC",
},
},
},
},
{
desc: "Multiple input addresses all with one or more records",
request: &types.QueryAddressUnbondings{
Address: "strideAddrUserA, cosmosAddrUserA",
},
response: &types.QueryAddressUnbondingsResponse{
AddressUnbondings: []types.AddressUnbonding{
{
Address: "strideAddrUserA",
Receiver: "strideAddrUserA",
Amount: sdk.NewInt(2000),
Denom: "ustrd",
EpochNumber: uint64(101),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-11 00:00:00 +0000 UTC",
},
{
Address: "cosmosAddrUserA",
Receiver: "cosmosAddrUserA",
Amount: sdk.NewInt(1200),
Denom: "uatom",
EpochNumber: uint64(101),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-27 00:00:00 +0000 UTC",
},
{
Address: "strideAddrUserA",
Receiver: "strideAddrUserA",
Amount: sdk.NewInt(5000),
Denom: "ustrd",
EpochNumber: uint64(110),
ClaimIsPending: false,
UnbondingEstimatedTime: "2024-01-11 00:00:00 +0000 UTC",
},
},
},
},
{
desc: "No address given, error expected",
request: &types.QueryAddressUnbondings{
Address: "",
},
response: nil,
err: status.Error(codes.InvalidArgument, "invalid request"),
},
} {
t.Run(tc.desc, func(t *testing.T) {
response, err := keeper.AddressUnbondings(wctx, tc.request)
if tc.err != nil {
require.ErrorIs(t, err, tc.err)
} else {
require.NoError(t, err)
require.Equal(t,
tc.response,
response,
)
}
})
}
}

0 comments on commit 910b667

Please sign in to comment.