mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 04:37:26 +00:00 
			
		
		
		
	Add specific /vaults/bkava and /deposits query handler to get aggregated bkava amounts (#1293)
				
					
				
			* Use custom aggregate handler for querying 'bkava' vault * Add 3rd bkava vault * Add special kava deposit handlers * Separate bkava logic to parent deposits handler * Rename single vault/account queries * Remove all deposits queries * Include empty vaults in /vaults query * Respond with empty values when querying account deposits with no deposits * return ukava value in bkava vault queries * remove refernce to specific staked token denom * return ukava value in bkava deposit queries Co-authored-by: rhuairahrighairigh <ruaridh.odonnell@gmail.com>
This commit is contained in:
		
							parent
							
								
									ed116b24ba
								
							
						
					
					
						commit
						9fb64b1f11
					
				@ -615,6 +615,7 @@ func NewApp(
 | 
			
		||||
		earnSubspace,
 | 
			
		||||
		app.accountKeeper,
 | 
			
		||||
		app.bankKeeper,
 | 
			
		||||
		app.liquidKeeper,
 | 
			
		||||
		&hardKeeper,
 | 
			
		||||
		&savingsKeeper,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
@ -170,7 +170,7 @@ func (suite *depositTestSuite) TestDeposit_PrivateVault() {
 | 
			
		||||
 | 
			
		||||
func (suite *depositTestSuite) TestDeposit_bKava() {
 | 
			
		||||
	vaultDenom := "bkava"
 | 
			
		||||
	coinDenom := vaultDenom + "-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
 | 
			
		||||
	coinDenom := testutil.TestBkavaDenoms[0]
 | 
			
		||||
 | 
			
		||||
	startBalance := sdk.NewInt64Coin(coinDenom, 1000)
 | 
			
		||||
	depositAmount := sdk.NewInt64Coin(coinDenom, 100)
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,12 @@ package keeper
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/grpc/codes"
 | 
			
		||||
	"google.golang.org/grpc/status"
 | 
			
		||||
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/store/prefix"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/types/query"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/earn/types"
 | 
			
		||||
)
 | 
			
		||||
@ -51,6 +50,14 @@ func (s queryServer) Vaults(
 | 
			
		||||
 | 
			
		||||
	sdkCtx := sdk.UnwrapSDKContext(ctx)
 | 
			
		||||
 | 
			
		||||
	allowedVaults := s.keeper.GetAllowedVaults(sdkCtx)
 | 
			
		||||
	allowedVaultsMap := make(map[string]types.AllowedVault)
 | 
			
		||||
	visitedMap := make(map[string]bool)
 | 
			
		||||
	for _, av := range allowedVaults {
 | 
			
		||||
		allowedVaultsMap[av.Denom] = av
 | 
			
		||||
		visitedMap[av.Denom] = false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vaults := []types.VaultResponse{}
 | 
			
		||||
 | 
			
		||||
	var vaultRecordsErr error
 | 
			
		||||
@ -58,9 +65,16 @@ func (s queryServer) Vaults(
 | 
			
		||||
	// Iterate over vault records instead of AllowedVaults to get all bkava-*
 | 
			
		||||
	// vaults
 | 
			
		||||
	s.keeper.IterateVaultRecords(sdkCtx, func(record types.VaultRecord) bool {
 | 
			
		||||
		allowedVault, found := s.keeper.GetAllowedVault(sdkCtx, record.TotalShares.Denom)
 | 
			
		||||
		// Check if bkava, use allowed vault
 | 
			
		||||
		allowedVaultDenom := record.TotalShares.Denom
 | 
			
		||||
		if strings.HasPrefix(record.TotalShares.Denom, bkavaPrefix) {
 | 
			
		||||
			allowedVaultDenom = bkavaDenom
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		allowedVault, found := allowedVaultsMap[allowedVaultDenom]
 | 
			
		||||
		if !found {
 | 
			
		||||
			vaultRecordsErr = fmt.Errorf("vault record not found for vault record denom %s", record.TotalShares.Denom)
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		totalValue, err := s.keeper.GetVaultTotalValue(sdkCtx, record.TotalShares.Denom)
 | 
			
		||||
@ -79,6 +93,9 @@ func (s queryServer) Vaults(
 | 
			
		||||
			TotalValue:        totalValue.Amount,
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		// Mark this allowed vault as visited
 | 
			
		||||
		visitedMap[allowedVaultDenom] = true
 | 
			
		||||
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
@ -86,6 +103,30 @@ func (s queryServer) Vaults(
 | 
			
		||||
		return nil, vaultRecordsErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add the allowed vaults that have not been visited yet
 | 
			
		||||
	// These are always empty vaults, as the vault would have been visited
 | 
			
		||||
	// earlier if there are any deposits
 | 
			
		||||
	for denom, visited := range visitedMap {
 | 
			
		||||
		if visited {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		allowedVault, found := allowedVaultsMap[denom]
 | 
			
		||||
		if !found {
 | 
			
		||||
			return nil, fmt.Errorf("vault record not found for vault record denom %s", denom)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vaults = append(vaults, types.VaultResponse{
 | 
			
		||||
			Denom:             denom,
 | 
			
		||||
			Strategies:        allowedVault.Strategies,
 | 
			
		||||
			IsPrivateVault:    allowedVault.IsPrivateVault,
 | 
			
		||||
			AllowedDepositors: addressSliceToStringSlice(allowedVault.AllowedDepositors),
 | 
			
		||||
			// No shares, no value
 | 
			
		||||
			TotalShares: sdk.ZeroDec().String(),
 | 
			
		||||
			TotalValue:  sdk.ZeroInt(),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Does not include vaults that have no deposits, only iterates over vault
 | 
			
		||||
	// records which exists only for those with deposits.
 | 
			
		||||
	return &types.QueryVaultsResponse{
 | 
			
		||||
@ -114,6 +155,11 @@ func (s queryServer) Vault(
 | 
			
		||||
		return nil, status.Errorf(codes.NotFound, "vault not found with specified denom")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Handle bkava separately to get total of **all** bkava vaults
 | 
			
		||||
	if req.Denom == "bkava" {
 | 
			
		||||
		return s.getAggregateBkavaVault(sdkCtx, allowedVault)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Must be req.Denom and not allowedVault.Denom to get full "bkava" denom
 | 
			
		||||
	vaultRecord, found := s.keeper.GetVaultRecord(sdkCtx, req.Denom)
 | 
			
		||||
	if !found {
 | 
			
		||||
@ -141,6 +187,54 @@ func (s queryServer) Vault(
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getAggregateBkavaVault returns a VaultResponse of the total of all bkava
 | 
			
		||||
// vaults.
 | 
			
		||||
func (s queryServer) getAggregateBkavaVault(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	allowedVault types.AllowedVault,
 | 
			
		||||
) (*types.QueryVaultResponse, error) {
 | 
			
		||||
	allBkava := sdk.NewCoins()
 | 
			
		||||
 | 
			
		||||
	var iterErr error
 | 
			
		||||
	s.keeper.IterateVaultRecords(ctx, func(record types.VaultRecord) (stop bool) {
 | 
			
		||||
		// Skip non bkava vaults
 | 
			
		||||
		if !strings.HasPrefix(record.TotalShares.Denom, "bkava") {
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		vaultValue, err := s.keeper.GetVaultTotalValue(ctx, record.TotalShares.Denom)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			iterErr = err
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		allBkava = allBkava.Add(vaultValue)
 | 
			
		||||
 | 
			
		||||
		return false
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if iterErr != nil {
 | 
			
		||||
		return nil, iterErr
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	vaultValue, err := s.keeper.liquidKeeper.GetStakedTokensForDerivatives(ctx, allBkava)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.QueryVaultResponse{
 | 
			
		||||
		Vault: types.VaultResponse{
 | 
			
		||||
			Denom:             "bkava",
 | 
			
		||||
			Strategies:        allowedVault.Strategies,
 | 
			
		||||
			IsPrivateVault:    allowedVault.IsPrivateVault,
 | 
			
		||||
			AllowedDepositors: addressSliceToStringSlice(allowedVault.AllowedDepositors),
 | 
			
		||||
			// Empty for shares, as adding up all shares is not useful information
 | 
			
		||||
			TotalShares: "0",
 | 
			
		||||
			TotalValue:  vaultValue.Amount,
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Deposits implements the gRPC service handler for querying x/earn deposits.
 | 
			
		||||
func (s queryServer) Deposits(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
@ -150,30 +244,29 @@ func (s queryServer) Deposits(
 | 
			
		||||
		return nil, status.Errorf(codes.InvalidArgument, "empty request")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.Depositor == "" {
 | 
			
		||||
		return nil, status.Errorf(codes.InvalidArgument, "depositor is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sdkCtx := sdk.UnwrapSDKContext(ctx)
 | 
			
		||||
 | 
			
		||||
	// 1. Specific account and specific vault
 | 
			
		||||
	if req.Depositor != "" && req.Denom != "" {
 | 
			
		||||
		return s.getAccountVaultDeposit(sdkCtx, req)
 | 
			
		||||
	// bkava aggregate total
 | 
			
		||||
	if req.Denom == "bkava" {
 | 
			
		||||
		return s.getOneAccountBkavaVaultDeposit(sdkCtx, req)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 2. All accounts, specific vault
 | 
			
		||||
	if req.Depositor == "" && req.Denom != "" {
 | 
			
		||||
		return s.getVaultAllDeposits(sdkCtx, req)
 | 
			
		||||
	// specific vault
 | 
			
		||||
	if req.Denom != "" {
 | 
			
		||||
		return s.getOneAccountOneVaultDeposit(sdkCtx, req)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 3. Specific account, all vaults
 | 
			
		||||
	if req.Depositor != "" && req.Denom == "" {
 | 
			
		||||
		return s.getAccountAllDeposits(sdkCtx, req)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 4. All accounts, all vaults
 | 
			
		||||
	return s.getAllDeposits(sdkCtx, req)
 | 
			
		||||
	// all vaults
 | 
			
		||||
	return s.getOneAccountAllDeposits(sdkCtx, req)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getAccountVaultDeposit returns deposits for a specific vault and a specific
 | 
			
		||||
// getOneAccountOneVaultDeposit returns deposits for a specific vault and a specific
 | 
			
		||||
// account
 | 
			
		||||
func (s queryServer) getAccountVaultDeposit(
 | 
			
		||||
func (s queryServer) getOneAccountOneVaultDeposit(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	req *types.QueryDepositsRequest,
 | 
			
		||||
) (*types.QueryDepositsResponse, error) {
 | 
			
		||||
@ -184,90 +277,95 @@ func (s queryServer) getAccountVaultDeposit(
 | 
			
		||||
 | 
			
		||||
	shareRecord, found := s.keeper.GetVaultShareRecord(ctx, depositor)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return nil, status.Error(codes.NotFound, "No deposit found for owner")
 | 
			
		||||
		return &types.QueryDepositsResponse{
 | 
			
		||||
			Deposits: []types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: depositor.String(),
 | 
			
		||||
					// Zero shares and zero value for no deposits
 | 
			
		||||
					Shares: types.NewVaultShares(types.NewVaultShare(req.Denom, sdk.ZeroDec())),
 | 
			
		||||
					Value:  sdk.NewCoins(sdk.NewCoin(req.Denom, sdk.ZeroInt())),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Pagination: nil,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if shareRecord.Shares.AmountOf(req.Denom).IsZero() {
 | 
			
		||||
		return nil, status.Error(codes.NotFound, fmt.Sprintf("No deposit for denom %s found for owner", req.Denom))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value, err := getAccountValue(ctx, s.keeper, depositor, shareRecord.Shares)
 | 
			
		||||
	// Only requesting the value of the specified denom
 | 
			
		||||
	value, err := s.keeper.GetVaultAccountValue(ctx, req.Denom, depositor)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, status.Error(codes.InvalidArgument, err.Error())
 | 
			
		||||
		return nil, status.Error(codes.NotFound, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.QueryDepositsResponse{
 | 
			
		||||
		Deposits: []types.DepositResponse{
 | 
			
		||||
			{
 | 
			
		||||
				Depositor: depositor.String(),
 | 
			
		||||
				Shares:    shareRecord.Shares,
 | 
			
		||||
				Value:     value,
 | 
			
		||||
				// Only respond with requested denom shares
 | 
			
		||||
				Shares: types.NewVaultShares(
 | 
			
		||||
					types.NewVaultShare(req.Denom, shareRecord.Shares.AmountOf(req.Denom)),
 | 
			
		||||
				),
 | 
			
		||||
				Value: sdk.NewCoins(value),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Pagination: nil,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getVaultAllDeposits returns all deposits for a specific vault
 | 
			
		||||
func (s queryServer) getVaultAllDeposits(
 | 
			
		||||
// getOneAccountBkavaVaultDeposit returns deposits for the aggregated bkava vault
 | 
			
		||||
// and a specific account
 | 
			
		||||
func (s queryServer) getOneAccountBkavaVaultDeposit(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	req *types.QueryDepositsRequest,
 | 
			
		||||
) (*types.QueryDepositsResponse, error) {
 | 
			
		||||
	_, found := s.keeper.GetVaultRecord(ctx, req.Denom)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return nil, status.Error(codes.NotFound, "Vault record for denom not found")
 | 
			
		||||
	depositor, err := sdk.AccAddressFromBech32(req.Depositor)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, status.Error(codes.InvalidArgument, "Invalid address")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	deposits := []types.DepositResponse{}
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(s.keeper.key), types.VaultShareRecordKeyPrefix)
 | 
			
		||||
	shareRecord, found := s.keeper.GetVaultShareRecord(ctx, depositor)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return &types.QueryDepositsResponse{
 | 
			
		||||
			Deposits: []types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: depositor.String(),
 | 
			
		||||
					// Zero shares and zero value for no deposits
 | 
			
		||||
					Shares: types.NewVaultShares(types.NewVaultShare(req.Denom, sdk.ZeroDec())),
 | 
			
		||||
					Value:  sdk.NewCoins(sdk.NewCoin(req.Denom, sdk.ZeroInt())),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Pagination: nil,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pageRes, err := query.FilteredPaginate(
 | 
			
		||||
		store,
 | 
			
		||||
		req.Pagination,
 | 
			
		||||
		func(key []byte, value []byte, accumulate bool) (bool, error) {
 | 
			
		||||
			var record types.VaultShareRecord
 | 
			
		||||
			err := s.keeper.cdc.Unmarshal(value, &record)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return false, err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Only those that have amount of requested denom
 | 
			
		||||
			if record.Shares.AmountOf(req.Denom).IsZero() {
 | 
			
		||||
				// inform paginate that there was no match on this key
 | 
			
		||||
				return false, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if accumulate {
 | 
			
		||||
				accValue, err := getAccountValue(ctx, s.keeper, record.Depositor, record.Shares)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return false, err
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				// only add to results if paginate tells us to
 | 
			
		||||
				deposits = append(deposits, types.DepositResponse{
 | 
			
		||||
					Depositor: record.Depositor.String(),
 | 
			
		||||
					Shares:    record.Shares,
 | 
			
		||||
					Value:     accValue,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// inform paginate that were was a match on this key
 | 
			
		||||
			return true, nil
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	// Get all account deposit values to add up bkava
 | 
			
		||||
	totalAccountValue, err := getAccountTotalValue(ctx, s.keeper, depositor, shareRecord.Shares)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Use account value with only the aggregate bkava converted to underlying staked tokens
 | 
			
		||||
	stakedValue, err := s.keeper.liquidKeeper.GetStakedTokensForDerivatives(ctx, totalAccountValue)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.QueryDepositsResponse{
 | 
			
		||||
		Deposits:   deposits,
 | 
			
		||||
		Pagination: pageRes,
 | 
			
		||||
		Deposits: []types.DepositResponse{
 | 
			
		||||
			{
 | 
			
		||||
				Depositor: depositor.String(),
 | 
			
		||||
				// Only respond with requested denom shares
 | 
			
		||||
				Shares: types.NewVaultShares(
 | 
			
		||||
					types.NewVaultShare(req.Denom, shareRecord.Shares.AmountOf(req.Denom)),
 | 
			
		||||
				),
 | 
			
		||||
				Value: sdk.NewCoins(stakedValue),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Pagination: nil,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getAccountAllDeposits returns deposits for all vaults for a specific account
 | 
			
		||||
func (s queryServer) getAccountAllDeposits(
 | 
			
		||||
// getOneAccountAllDeposits returns deposits for all vaults for a specific account
 | 
			
		||||
func (s queryServer) getOneAccountAllDeposits(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	req *types.QueryDepositsRequest,
 | 
			
		||||
) (*types.QueryDepositsResponse, error) {
 | 
			
		||||
@ -280,10 +378,13 @@ func (s queryServer) getAccountAllDeposits(
 | 
			
		||||
 | 
			
		||||
	accountShare, found := s.keeper.GetVaultShareRecord(ctx, depositor)
 | 
			
		||||
	if !found {
 | 
			
		||||
		return nil, status.Error(codes.NotFound, "No deposit found for depositor")
 | 
			
		||||
		return &types.QueryDepositsResponse{
 | 
			
		||||
			Deposits:   []types.DepositResponse{},
 | 
			
		||||
			Pagination: nil,
 | 
			
		||||
		}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	value, err := getAccountValue(ctx, s.keeper, depositor, accountShare.Shares)
 | 
			
		||||
	value, err := getAccountTotalValue(ctx, s.keeper, depositor, accountShare.Shares)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, status.Error(codes.InvalidArgument, err.Error())
 | 
			
		||||
	}
 | 
			
		||||
@ -300,51 +401,9 @@ func (s queryServer) getAccountAllDeposits(
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getAllDeposits returns all deposits for all vaults
 | 
			
		||||
func (s queryServer) getAllDeposits(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	req *types.QueryDepositsRequest,
 | 
			
		||||
) (*types.QueryDepositsResponse, error) {
 | 
			
		||||
	deposits := []types.DepositResponse{}
 | 
			
		||||
	store := prefix.NewStore(ctx.KVStore(s.keeper.key), types.VaultShareRecordKeyPrefix)
 | 
			
		||||
 | 
			
		||||
	pageRes, err := query.Paginate(
 | 
			
		||||
		store,
 | 
			
		||||
		req.Pagination,
 | 
			
		||||
		func(key []byte, value []byte) error {
 | 
			
		||||
			var record types.VaultShareRecord
 | 
			
		||||
			err := s.keeper.cdc.Unmarshal(value, &record)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			accValue, err := getAccountValue(ctx, s.keeper, record.Depositor, record.Shares)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// only add to results if paginate tells us to
 | 
			
		||||
			deposits = append(deposits, types.DepositResponse{
 | 
			
		||||
				Depositor: record.Depositor.String(),
 | 
			
		||||
				Shares:    record.Shares,
 | 
			
		||||
				Value:     accValue,
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			return nil
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &types.QueryDepositsResponse{
 | 
			
		||||
		Deposits:   deposits,
 | 
			
		||||
		Pagination: pageRes,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getAccountValue(
 | 
			
		||||
// getAccountTotalValue returns the total value for all vaults for a specific
 | 
			
		||||
// account based on their shares.
 | 
			
		||||
func getAccountTotalValue(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	keeper Keeper,
 | 
			
		||||
	account sdk.AccAddress,
 | 
			
		||||
 | 
			
		||||
@ -5,14 +5,19 @@ import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/baseapp"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/x/staking"
 | 
			
		||||
	stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
 | 
			
		||||
	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
 | 
			
		||||
	"github.com/stretchr/testify/suite"
 | 
			
		||||
	"google.golang.org/grpc/codes"
 | 
			
		||||
	"google.golang.org/grpc/status"
 | 
			
		||||
 | 
			
		||||
	"github.com/kava-labs/kava/x/earn/keeper"
 | 
			
		||||
	"github.com/kava-labs/kava/x/earn/testutil"
 | 
			
		||||
	"github.com/kava-labs/kava/x/earn/types"
 | 
			
		||||
	"github.com/stretchr/testify/suite"
 | 
			
		||||
	liquidtypes "github.com/kava-labs/kava/x/liquid/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type grpcQueryTestSuite struct {
 | 
			
		||||
@ -81,13 +86,32 @@ func (suite *grpcQueryTestSuite) TestVaults_ZeroSupply() {
 | 
			
		||||
	suite.Run("all", func() {
 | 
			
		||||
		res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Empty(res.Vaults)
 | 
			
		||||
		suite.Require().ElementsMatch([]types.VaultResponse{
 | 
			
		||||
			{
 | 
			
		||||
				Denom:             "usdx",
 | 
			
		||||
				Strategies:        []types.StrategyType{types.STRATEGY_TYPE_HARD},
 | 
			
		||||
				IsPrivateVault:    false,
 | 
			
		||||
				AllowedDepositors: nil,
 | 
			
		||||
				TotalShares:       sdk.ZeroDec().String(),
 | 
			
		||||
				TotalValue:        sdk.ZeroInt(),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Denom:             "busd",
 | 
			
		||||
				Strategies:        []types.StrategyType{types.STRATEGY_TYPE_HARD},
 | 
			
		||||
				IsPrivateVault:    false,
 | 
			
		||||
				AllowedDepositors: nil,
 | 
			
		||||
				TotalShares:       sdk.ZeroDec().String(),
 | 
			
		||||
				TotalValue:        sdk.ZeroInt(),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
			res.Vaults,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestVaults_WithSupply() {
 | 
			
		||||
	vaultDenom := "usdx"
 | 
			
		||||
	vault2Denom := "bkava-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
 | 
			
		||||
	vault2Denom := testutil.TestBkavaDenoms[0]
 | 
			
		||||
 | 
			
		||||
	depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
 | 
			
		||||
	deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100)
 | 
			
		||||
@ -132,6 +156,60 @@ func (suite *grpcQueryTestSuite) TestVaults_WithSupply() {
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestVaults_MixedSupply() {
 | 
			
		||||
	vaultDenom := "usdx"
 | 
			
		||||
	vault2Denom := "busd"
 | 
			
		||||
	vault3Denom := testutil.TestBkavaDenoms[0]
 | 
			
		||||
 | 
			
		||||
	depositAmount := sdk.NewInt64Coin(vault3Denom, 100)
 | 
			
		||||
 | 
			
		||||
	suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
 | 
			
		||||
	suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
 | 
			
		||||
	suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
 | 
			
		||||
 | 
			
		||||
	acc := suite.CreateAccount(sdk.NewCoins(
 | 
			
		||||
		sdk.NewInt64Coin(vaultDenom, 1000),
 | 
			
		||||
		sdk.NewInt64Coin(vault2Denom, 1000),
 | 
			
		||||
		sdk.NewInt64Coin(vault3Denom, 1000),
 | 
			
		||||
	), 0)
 | 
			
		||||
 | 
			
		||||
	err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	suite.Require().Len(res.Vaults, 3)
 | 
			
		||||
	suite.Require().ElementsMatch(
 | 
			
		||||
		[]types.VaultResponse{
 | 
			
		||||
			{
 | 
			
		||||
				Denom:             vaultDenom,
 | 
			
		||||
				Strategies:        []types.StrategyType{types.STRATEGY_TYPE_HARD},
 | 
			
		||||
				IsPrivateVault:    false,
 | 
			
		||||
				AllowedDepositors: nil,
 | 
			
		||||
				TotalShares:       sdk.ZeroDec().String(),
 | 
			
		||||
				TotalValue:        sdk.ZeroInt(),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Denom:             vault2Denom,
 | 
			
		||||
				Strategies:        []types.StrategyType{types.STRATEGY_TYPE_HARD},
 | 
			
		||||
				IsPrivateVault:    false,
 | 
			
		||||
				AllowedDepositors: nil,
 | 
			
		||||
				TotalShares:       sdk.ZeroDec().String(),
 | 
			
		||||
				TotalValue:        sdk.ZeroInt(),
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Denom:             vault3Denom,
 | 
			
		||||
				Strategies:        []types.StrategyType{types.STRATEGY_TYPE_SAVINGS},
 | 
			
		||||
				IsPrivateVault:    false,
 | 
			
		||||
				AllowedDepositors: nil,
 | 
			
		||||
				TotalShares:       depositAmount.Amount.ToDec().String(),
 | 
			
		||||
				TotalValue:        depositAmount.Amount,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		res.Vaults,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestVault_NotFound() {
 | 
			
		||||
	_, err := suite.queryClient.Vault(context.Background(), types.NewQueryVaultRequest("usdx"))
 | 
			
		||||
	suite.Require().Error(err)
 | 
			
		||||
@ -141,7 +219,7 @@ func (suite *grpcQueryTestSuite) TestVault_NotFound() {
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
	vault1Denom := "usdx"
 | 
			
		||||
	vault2Denom := "busd"
 | 
			
		||||
	vault3Denom := "bkava-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
 | 
			
		||||
	vault3Denom := testutil.TestBkavaDenoms[0]
 | 
			
		||||
 | 
			
		||||
	// Add vaults
 | 
			
		||||
	suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
 | 
			
		||||
@ -153,6 +231,7 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
		sdk.NewInt64Coin(vault2Denom, 1000),
 | 
			
		||||
		sdk.NewInt64Coin(vault3Denom, 1000),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100)
 | 
			
		||||
	deposit2Amount := sdk.NewInt64Coin(vault2Denom, 200)
 | 
			
		||||
	deposit3Amount := sdk.NewInt64Coin(vault3Denom, 200)
 | 
			
		||||
@ -163,7 +242,7 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
 | 
			
		||||
	// Deposit into each vault from each account - 4 total deposits
 | 
			
		||||
	// Acc 1: usdx + busd
 | 
			
		||||
	// Acc 2: usdx + usdc
 | 
			
		||||
	// Acc 2: usdx + bkava
 | 
			
		||||
	err := suite.Keeper.Deposit(suite.Ctx, acc1, deposit1Amount, types.STRATEGY_TYPE_HARD)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, acc1, deposit2Amount, types.STRATEGY_TYPE_HARD)
 | 
			
		||||
@ -174,7 +253,7 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, acc2, deposit3Amount, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	suite.Run("1) 1 vault for 1 account", func() {
 | 
			
		||||
	suite.Run("specific vault", func() {
 | 
			
		||||
		// Query all deposits for account 1
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
@ -186,12 +265,12 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
			[]types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: acc1.String(),
 | 
			
		||||
					// Still includes all deposits
 | 
			
		||||
					// Only includes specified deposit shares
 | 
			
		||||
					Shares: types.NewVaultShares(
 | 
			
		||||
						types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()),
 | 
			
		||||
						types.NewVaultShare(deposit2Amount.Denom, deposit2Amount.Amount.ToDec()),
 | 
			
		||||
					),
 | 
			
		||||
					Value: sdk.NewCoins(deposit1Amount, deposit2Amount),
 | 
			
		||||
					// Only the specified vault denom value
 | 
			
		||||
					Value: sdk.NewCoins(deposit1Amount),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
@ -200,16 +279,41 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	suite.Run("1) invalid vault for 1 account", func() {
 | 
			
		||||
	suite.Run("specific bkava vault", func() {
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest(acc2.String(), vault3Denom, nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Len(res.Deposits, 1)
 | 
			
		||||
		suite.Require().ElementsMatchf(
 | 
			
		||||
			[]types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: acc2.String(),
 | 
			
		||||
					// Only includes specified deposit shares
 | 
			
		||||
					Shares: types.NewVaultShares(
 | 
			
		||||
						types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()),
 | 
			
		||||
					),
 | 
			
		||||
					// Only the specified vault denom value
 | 
			
		||||
					Value: sdk.NewCoins(deposit3Amount),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
			"deposits should match, got %v",
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	suite.Run("invalid vault", func() {
 | 
			
		||||
		_, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest(acc1.String(), "notavaliddenom", nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().Error(err)
 | 
			
		||||
		suite.Require().ErrorIs(err, status.Errorf(codes.NotFound, "No deposit for denom notavaliddenom found for owner"))
 | 
			
		||||
		suite.Require().ErrorIs(err, status.Errorf(codes.NotFound, "vault for notavaliddenom not found"))
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	suite.Run("3) all vaults for 1 account", func() {
 | 
			
		||||
	suite.Run("all vaults", func() {
 | 
			
		||||
		// Query all deposits for account 1
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
@ -231,55 +335,35 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	suite.Run("2) all accounts, specific vault", func() {
 | 
			
		||||
		// Query all deposits for vault 3
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestDeposits_NoDeposits() {
 | 
			
		||||
	vault1Denom := "usdx"
 | 
			
		||||
	vault2Denom := "busd"
 | 
			
		||||
 | 
			
		||||
	// Add vaults
 | 
			
		||||
	suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
 | 
			
		||||
	suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
 | 
			
		||||
	suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
 | 
			
		||||
 | 
			
		||||
	// Accounts
 | 
			
		||||
	acc1 := suite.CreateAccount(sdk.NewCoins(), 0).GetAddress()
 | 
			
		||||
 | 
			
		||||
	suite.Run("specific vault", func() {
 | 
			
		||||
		// Query all deposits for account 1
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest("", vault3Denom, nil),
 | 
			
		||||
			types.NewQueryDepositsRequest(acc1.String(), vault1Denom, nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Len(res.Deposits, 1)
 | 
			
		||||
		suite.Require().ElementsMatch(
 | 
			
		||||
			[]types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: acc2.String(),
 | 
			
		||||
					Shares: types.NewVaultShares(
 | 
			
		||||
						types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()),
 | 
			
		||||
						types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()),
 | 
			
		||||
					),
 | 
			
		||||
					Value: sdk.NewCoins(deposit1Amount, deposit3Amount),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	suite.Run("4) all vaults and all accounts", func() {
 | 
			
		||||
		// Query all deposits for all vaults
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest("", "", nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Len(res.Deposits, 2)
 | 
			
		||||
		suite.Require().ElementsMatchf(
 | 
			
		||||
			[]types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: acc1.String(),
 | 
			
		||||
					Shares: types.NewVaultShares(
 | 
			
		||||
						types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()),
 | 
			
		||||
						types.NewVaultShare(deposit2Amount.Denom, deposit2Amount.Amount.ToDec()),
 | 
			
		||||
					),
 | 
			
		||||
					Value: sdk.NewCoins(deposit1Amount, deposit2Amount),
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: acc2.String(),
 | 
			
		||||
					Shares: types.NewVaultShares(
 | 
			
		||||
						types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()),
 | 
			
		||||
						types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()),
 | 
			
		||||
					),
 | 
			
		||||
					Value: sdk.NewCoins(deposit1Amount, deposit3Amount),
 | 
			
		||||
					// Zero shares and zero value
 | 
			
		||||
					Shares: nil,
 | 
			
		||||
					Value:  nil,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
@ -287,15 +371,25 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	suite.Run("all vaults", func() {
 | 
			
		||||
		// Query all deposits for account 1
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest(acc1.String(), "", nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Empty(res.Deposits)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestDeposits_NotFound() {
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestDeposits_NoDepositor() {
 | 
			
		||||
	_, err := suite.queryClient.Deposits(
 | 
			
		||||
		context.Background(),
 | 
			
		||||
		types.NewQueryDepositsRequest("", "usdx", nil),
 | 
			
		||||
	)
 | 
			
		||||
	suite.Require().Error(err)
 | 
			
		||||
	suite.Require().ErrorIs(err, status.Error(codes.NotFound, "Vault record for denom not found"))
 | 
			
		||||
	suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "depositor is required"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestDeposits_InvalidAddress() {
 | 
			
		||||
@ -314,9 +408,89 @@ func (suite *grpcQueryTestSuite) TestDeposits_InvalidAddress() {
 | 
			
		||||
	suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "Invalid address"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestVault_bKava() {
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestDeposits_bKava() {
 | 
			
		||||
	// vault denom is only "bkava" which has it's own special handler
 | 
			
		||||
	suite.CreateVault(
 | 
			
		||||
		"bkava",
 | 
			
		||||
		types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
 | 
			
		||||
		false,
 | 
			
		||||
		[]sdk.AccAddress{},
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	address1, derivatives1, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[0], sdk.NewInt(1e9))
 | 
			
		||||
	address2, derivatives2, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[1], sdk.NewInt(1e9))
 | 
			
		||||
 | 
			
		||||
	// Slash the last validator to reduce the value of it's derivatives to test bkava to underlying token conversion.
 | 
			
		||||
	// First call end block to bond validator to enable slashing.
 | 
			
		||||
	staking.EndBlocker(suite.Ctx, suite.App.GetStakingKeeper())
 | 
			
		||||
	err := suite.slashValidator(sdk.ValAddress(address2), sdk.MustNewDecFromStr("0.5"))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	suite.Run("no deposits", func() {
 | 
			
		||||
		// Query all deposits for account 1
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest(address1.String(), "bkava", nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Len(res.Deposits, 1)
 | 
			
		||||
		suite.Require().ElementsMatchf(
 | 
			
		||||
			[]types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: address1.String(),
 | 
			
		||||
					// Zero shares for "bkava" aggregate
 | 
			
		||||
					Shares: nil,
 | 
			
		||||
					// Only the specified vault denom value
 | 
			
		||||
					Value: nil,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
			"deposits should match, got %v",
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, address1, derivatives1, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	err = suite.BankKeeper.SendCoins(suite.Ctx, address2, address1, sdk.NewCoins(derivatives2))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, address1, derivatives2, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	suite.Run("multiple deposits", func() {
 | 
			
		||||
		// Query all deposits for account 1
 | 
			
		||||
		res, err := suite.queryClient.Deposits(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			types.NewQueryDepositsRequest(address1.String(), "bkava", nil),
 | 
			
		||||
		)
 | 
			
		||||
		suite.Require().NoError(err)
 | 
			
		||||
		suite.Require().Len(res.Deposits, 1)
 | 
			
		||||
		// first validator isn't slashed, so bkava units equal to underlying staked tokens
 | 
			
		||||
		// last validator slashed 50% so derivatives are worth half
 | 
			
		||||
		expectedValue := derivatives1.Amount.Add(derivatives2.Amount.QuoRaw(2))
 | 
			
		||||
		suite.Require().ElementsMatchf(
 | 
			
		||||
			[]types.DepositResponse{
 | 
			
		||||
				{
 | 
			
		||||
					Depositor: address1.String(),
 | 
			
		||||
					// Zero shares for "bkava" aggregate
 | 
			
		||||
					Shares: nil,
 | 
			
		||||
					// Value returned in units of staked token
 | 
			
		||||
					Value: sdk.NewCoins(
 | 
			
		||||
						sdk.NewCoin(suite.bondDenom(), expectedValue),
 | 
			
		||||
					),
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
			"deposits should match, got %v",
 | 
			
		||||
			res.Deposits,
 | 
			
		||||
		)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestVault_bKava_Single() {
 | 
			
		||||
	vaultDenom := "bkava"
 | 
			
		||||
	coinDenom := vaultDenom + "-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
 | 
			
		||||
	coinDenom := testutil.TestBkavaDenoms[0]
 | 
			
		||||
 | 
			
		||||
	startBalance := sdk.NewInt64Coin(coinDenom, 1000)
 | 
			
		||||
	depositAmount := sdk.NewInt64Coin(coinDenom, 100)
 | 
			
		||||
@ -356,3 +530,132 @@ func (suite *grpcQueryTestSuite) TestVault_bKava() {
 | 
			
		||||
		res.Vault,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *grpcQueryTestSuite) TestVault_bKava_Aggregate() {
 | 
			
		||||
	vaultDenom := "bkava"
 | 
			
		||||
 | 
			
		||||
	address1, derivatives1, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[0], sdk.NewInt(1e9))
 | 
			
		||||
	address2, derivatives2, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[1], sdk.NewInt(1e9))
 | 
			
		||||
	address3, derivatives3, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[2], sdk.NewInt(1e9))
 | 
			
		||||
	// Slash the last validator to reduce the value of it's derivatives to test bkava to underlying token conversion.
 | 
			
		||||
	// First call end block to bond validator to enable slashing.
 | 
			
		||||
	staking.EndBlocker(suite.Ctx, suite.App.GetStakingKeeper())
 | 
			
		||||
	err := suite.slashValidator(sdk.ValAddress(address3), sdk.MustNewDecFromStr("0.5"))
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// vault denom is only "bkava" which has it's own special handler
 | 
			
		||||
	suite.CreateVault(
 | 
			
		||||
		vaultDenom,
 | 
			
		||||
		types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
 | 
			
		||||
		false,
 | 
			
		||||
		[]sdk.AccAddress{},
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, address1, derivatives1, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, address2, derivatives2, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	err = suite.Keeper.Deposit(suite.Ctx, address3, derivatives3, types.STRATEGY_TYPE_SAVINGS)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Query "bkava" to get aggregate amount
 | 
			
		||||
	res, err := suite.queryClient.Vault(
 | 
			
		||||
		context.Background(),
 | 
			
		||||
		types.NewQueryVaultRequest(vaultDenom),
 | 
			
		||||
	)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	// first two validators are not slashed, so bkava units equal to underlying staked tokens
 | 
			
		||||
	expectedValue := derivatives1.Amount.Add(derivatives2.Amount)
 | 
			
		||||
	// last validator slashed 50% so derivatives are worth half
 | 
			
		||||
	expectedValue = expectedValue.Add(derivatives2.Amount.QuoRaw(2))
 | 
			
		||||
	suite.Require().Equal(
 | 
			
		||||
		types.VaultResponse{
 | 
			
		||||
			Denom: vaultDenom,
 | 
			
		||||
			Strategies: types.StrategyTypes{
 | 
			
		||||
				types.STRATEGY_TYPE_SAVINGS,
 | 
			
		||||
			},
 | 
			
		||||
			IsPrivateVault:    false,
 | 
			
		||||
			AllowedDepositors: []string(nil),
 | 
			
		||||
			// No shares for aggregate
 | 
			
		||||
			TotalShares: "0",
 | 
			
		||||
			TotalValue:  expectedValue,
 | 
			
		||||
		},
 | 
			
		||||
		res.Vault,
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// createUnbondedValidator creates an unbonded validator with the given amount of self-delegation.
 | 
			
		||||
func (suite *grpcQueryTestSuite) createUnbondedValidator(address sdk.ValAddress, selfDelegation sdk.Coin, minSelfDelegation sdk.Int) error {
 | 
			
		||||
	msg, err := stakingtypes.NewMsgCreateValidator(
 | 
			
		||||
		address,
 | 
			
		||||
		ed25519.GenPrivKey().PubKey(),
 | 
			
		||||
		selfDelegation,
 | 
			
		||||
		stakingtypes.Description{},
 | 
			
		||||
		stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
 | 
			
		||||
		minSelfDelegation,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	msgServer := stakingkeeper.NewMsgServerImpl(suite.App.GetStakingKeeper())
 | 
			
		||||
	_, err = msgServer.CreateValidator(sdk.WrapSDKContext(suite.Ctx), msg)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// createAccountWithDerivatives creates an account with the given amount and denom of derivative token.
 | 
			
		||||
// Internally, it creates a validator account and mints derivatives from the validator's self delegation.
 | 
			
		||||
func (suite *grpcQueryTestSuite) createAccountWithDerivatives(denom string, amount sdk.Int) (sdk.AccAddress, sdk.Coin, sdk.Coins) {
 | 
			
		||||
	valAddress, err := liquidtypes.ParseLiquidStakingTokenDenom(denom)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
	address := sdk.AccAddress(valAddress)
 | 
			
		||||
 | 
			
		||||
	remainingSelfDelegation := sdk.NewInt(1e6)
 | 
			
		||||
	selfDelegation := sdk.NewCoin(
 | 
			
		||||
		suite.bondDenom(),
 | 
			
		||||
		amount.Add(remainingSelfDelegation),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	suite.NewAccountFromAddr(address, sdk.NewCoins(selfDelegation))
 | 
			
		||||
 | 
			
		||||
	err = suite.createUnbondedValidator(valAddress, selfDelegation, remainingSelfDelegation)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	toConvert := sdk.NewCoin(suite.bondDenom(), amount)
 | 
			
		||||
	derivatives, err := suite.App.GetLiquidKeeper().MintDerivative(suite.Ctx,
 | 
			
		||||
		address,
 | 
			
		||||
		valAddress,
 | 
			
		||||
		toConvert,
 | 
			
		||||
	)
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	fullBalance := suite.BankKeeper.GetAllBalances(suite.Ctx, address)
 | 
			
		||||
 | 
			
		||||
	return address, derivatives, fullBalance
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// slashValidator slashes the validator with the given address by the given percentage.
 | 
			
		||||
func (suite *grpcQueryTestSuite) slashValidator(address sdk.ValAddress, slashFraction sdk.Dec) error {
 | 
			
		||||
	stakingKeeper := suite.App.GetStakingKeeper()
 | 
			
		||||
 | 
			
		||||
	validator, found := stakingKeeper.GetValidator(suite.Ctx, address)
 | 
			
		||||
	suite.Require().True(found)
 | 
			
		||||
	consAddr, err := validator.GetConsAddr()
 | 
			
		||||
	suite.Require().NoError(err)
 | 
			
		||||
 | 
			
		||||
	// Assume infraction was at current height. Note unbonding delegations and redelegations are only slashed if created after
 | 
			
		||||
	// the infraction height so none will be slashed.
 | 
			
		||||
	infractionHeight := suite.Ctx.BlockHeight()
 | 
			
		||||
 | 
			
		||||
	power := stakingKeeper.TokensToConsensusPower(suite.Ctx, validator.GetTokens())
 | 
			
		||||
 | 
			
		||||
	stakingKeeper.Slash(suite.Ctx, consAddr, infractionHeight, power, slashFraction)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// bondDenom fetches the staking denom from the staking module.
 | 
			
		||||
func (suite *grpcQueryTestSuite) bondDenom() string {
 | 
			
		||||
	return suite.App.GetStakingKeeper().BondDenom(suite.Ctx)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ type Keeper struct {
 | 
			
		||||
	hooks         types.EarnHooks
 | 
			
		||||
	accountKeeper types.AccountKeeper
 | 
			
		||||
	bankKeeper    types.BankKeeper
 | 
			
		||||
	liquidKeeper  types.LiquidKeeper
 | 
			
		||||
 | 
			
		||||
	// Keepers used for strategies
 | 
			
		||||
	hardKeeper    types.HardKeeper
 | 
			
		||||
@ -29,6 +30,7 @@ func NewKeeper(
 | 
			
		||||
	paramstore paramtypes.Subspace,
 | 
			
		||||
	accountKeeper types.AccountKeeper,
 | 
			
		||||
	bankKeeper types.BankKeeper,
 | 
			
		||||
	liquidKeeper types.LiquidKeeper,
 | 
			
		||||
	hardKeeper types.HardKeeper,
 | 
			
		||||
	savingsKeeper types.SavingsKeeper,
 | 
			
		||||
) Keeper {
 | 
			
		||||
@ -42,6 +44,7 @@ func NewKeeper(
 | 
			
		||||
		paramSubspace: paramstore,
 | 
			
		||||
		accountKeeper: accountKeeper,
 | 
			
		||||
		bankKeeper:    bankKeeper,
 | 
			
		||||
		liquidKeeper:  liquidKeeper,
 | 
			
		||||
		hardKeeper:    hardKeeper,
 | 
			
		||||
		savingsKeeper: savingsKeeper,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -244,7 +244,7 @@ func (suite *withdrawTestSuite) TestWithdraw_Partial() {
 | 
			
		||||
 | 
			
		||||
func (suite *withdrawTestSuite) TestWithdraw_bKava() {
 | 
			
		||||
	vaultDenom := "bkava"
 | 
			
		||||
	coinDenom := vaultDenom + "-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
 | 
			
		||||
	coinDenom := testutil.TestBkavaDenoms[0]
 | 
			
		||||
 | 
			
		||||
	startBalance := sdk.NewInt64Coin(coinDenom, 1000)
 | 
			
		||||
	depositAmount := sdk.NewInt64Coin(coinDenom, 100)
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,12 @@ import (
 | 
			
		||||
	tmtime "github.com/tendermint/tendermint/types/time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var TestBkavaDenoms = []string{
 | 
			
		||||
	"bkava-kavavaloper15gqc744d05xacn4n6w2furuads9fu4pqn6zxlu",
 | 
			
		||||
	"bkava-kavavaloper15qdefkmwswysgg4qxgqpqr35k3m49pkx8yhpte",
 | 
			
		||||
	"bkava-kavavaloper1ypjp0m04pyp73hwgtc0dgkx0e9rrydeckewa42",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Suite implements a test suite for the earn module integration tests
 | 
			
		||||
type Suite struct {
 | 
			
		||||
	suite.Suite
 | 
			
		||||
@ -146,7 +152,9 @@ func (suite *Suite) SetupTest() {
 | 
			
		||||
		savingstypes.NewParams(
 | 
			
		||||
			[]string{
 | 
			
		||||
				"ukava",
 | 
			
		||||
				"bkava-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l",
 | 
			
		||||
				TestBkavaDenoms[0],
 | 
			
		||||
				TestBkavaDenoms[1],
 | 
			
		||||
				TestBkavaDenoms[2],
 | 
			
		||||
			},
 | 
			
		||||
		),
 | 
			
		||||
		nil,
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,11 @@ type BankKeeper interface {
 | 
			
		||||
	SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LiquidKeeper defines the expected interface needed for derivative to staked token conversions.
 | 
			
		||||
type LiquidKeeper interface {
 | 
			
		||||
	GetStakedTokensForDerivatives(ctx sdk.Context, derivatives sdk.Coins) (sdk.Coin, error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HardKeeper defines the expected interface needed for the hard strategy.
 | 
			
		||||
type HardKeeper interface {
 | 
			
		||||
	Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error
 | 
			
		||||
 | 
			
		||||
@ -111,28 +111,29 @@ func (k Keeper) IsDerivativeDenom(ctx sdk.Context, denom string) bool {
 | 
			
		||||
	return found
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetKavaForDerivatives returns the total amount of the provided derivatives
 | 
			
		||||
// in Kava accounting for the specific share prices.
 | 
			
		||||
func (k Keeper) GetKavaForDerivatives(ctx sdk.Context, coins sdk.Coins) (sdk.Int, error) {
 | 
			
		||||
	totalKava := sdk.ZeroInt()
 | 
			
		||||
// GetStakedTokensForDerivatives returns the total value of the provided derivatives
 | 
			
		||||
// in staked tokens, accounting for the specific share prices.
 | 
			
		||||
func (k Keeper) GetStakedTokensForDerivatives(ctx sdk.Context, coins sdk.Coins) (sdk.Coin, error) {
 | 
			
		||||
	total := sdk.ZeroInt()
 | 
			
		||||
 | 
			
		||||
	for _, coin := range coins {
 | 
			
		||||
		valAddr, err := types.ParseLiquidStakingTokenDenom(coin.Denom)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return sdk.Int{}, fmt.Errorf("invalid derivative denom: %w", err)
 | 
			
		||||
			return sdk.Coin{}, fmt.Errorf("invalid derivative denom: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		validator, found := k.stakingKeeper.GetValidator(ctx, valAddr)
 | 
			
		||||
		if !found {
 | 
			
		||||
			return sdk.Int{}, fmt.Errorf("invalid derivative denom %s: validator not found", coin.Denom)
 | 
			
		||||
			return sdk.Coin{}, fmt.Errorf("invalid derivative denom %s: validator not found", coin.Denom)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// bkava is 1:1 to delegation shares
 | 
			
		||||
		valTokens := validator.TokensFromSharesTruncated(coin.Amount.ToDec())
 | 
			
		||||
		totalKava = totalKava.Add(valTokens.TruncateInt())
 | 
			
		||||
		total = total.Add(valTokens.TruncateInt())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return totalKava, nil
 | 
			
		||||
	totalCoin := sdk.NewCoin(k.stakingKeeper.BondDenom(ctx), total)
 | 
			
		||||
	return totalCoin, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (k Keeper) mintCoins(ctx sdk.Context, receiver sdk.AccAddress, amount sdk.Coins) error {
 | 
			
		||||
 | 
			
		||||
@ -379,7 +379,7 @@ func (suite *KeeperTestSuite) TestIsDerivativeDenom() {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (suite *KeeperTestSuite) TestGetKavaForDerivatives() {
 | 
			
		||||
func (suite *KeeperTestSuite) TestGetStakedTokensForDerivatives() {
 | 
			
		||||
	_, addrs := app.GeneratePrivKeyAddressPairs(5)
 | 
			
		||||
	valAccAddr1, delegator, valAccAddr2, valAccAddr3 := addrs[0], addrs[1], addrs[2], addrs[3]
 | 
			
		||||
	valAddr1 := sdk.ValAddress(valAccAddr1)
 | 
			
		||||
@ -458,13 +458,13 @@ func (suite *KeeperTestSuite) TestGetKavaForDerivatives() {
 | 
			
		||||
 | 
			
		||||
	for _, tc := range testCases {
 | 
			
		||||
		suite.Run(tc.name, func() {
 | 
			
		||||
			kavaAmount, err := suite.Keeper.GetKavaForDerivatives(suite.Ctx, tc.derivatives)
 | 
			
		||||
			kavaAmount, err := suite.Keeper.GetStakedTokensForDerivatives(suite.Ctx, tc.derivatives)
 | 
			
		||||
 | 
			
		||||
			if tc.err != nil {
 | 
			
		||||
				suite.Require().Error(err)
 | 
			
		||||
			} else {
 | 
			
		||||
				suite.Require().NoError(err)
 | 
			
		||||
				suite.Require().Equal(tc.wantKavaAmount, kavaAmount)
 | 
			
		||||
				suite.Require().Equal(suite.NewBondCoin(tc.wantKavaAmount), kavaAmount)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user