Add savings strategy (#1290)

* Add basic savings strategy

Supports ukava

* Use clearer error message for ErrInvalidVaultStrategy

* Add invariants

* Separate specific vault/all vaults query, update depositor inconsistencies

* Update swagger

* Use single bkava AllowedVault for all bkava variants

* Do not use allowedVault.Denom for value

* Fix vault balance query

* Update query to list bkava vaults

* Add vaults query doc

* Update grpc query test with no supply

* Add earn hooks

* Handle errors

* Update outdated doc comments, make getAllowedVault_Raw private

* Fix outdated comments, lints

* Fix comment maths

* Use AccAddressFromBech32 to validate message addresses
This commit is contained in:
Derrick Lee 2022-09-12 10:50:35 -07:00 committed by GitHub
parent 8593d26c24
commit ded692d2f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2399 additions and 317 deletions

View File

@ -6962,7 +6962,7 @@ paths:
- Committee
/kava/earn/v1beta1/deposits:
get:
summary: Deposits queries deposit details based on owner address and vault
summary: Deposits queries deposit details based on depositor address and vault
operationId: EarnDeposits
responses:
'200':
@ -7067,8 +7067,8 @@ paths:
type: string
format: byte
parameters:
- name: owner
description: owner optionally filters deposits by owner.
- name: depositor
description: depositor optionally filters deposits by depositor.
in: query
required: false
type: string
@ -7237,9 +7237,9 @@ paths:
format: byte
tags:
- Earn
/kava/earn/v1beta1/vaults/{denom}:
/kava/earn/v1beta1/vaults:
get:
summary: Vaults queries vaults based on vault denom
summary: Vaults queries all vaults
operationId: EarnVaults
responses:
'200':
@ -7336,6 +7336,105 @@ paths:
value:
type: string
format: byte
tags:
- Earn
/kava/earn/v1beta1/vaults/{denom}:
get:
summary: Vault queries a single vault based on the vault denom
operationId: EarnVault
responses:
'200':
description: A successful response.
schema:
type: object
properties:
vault:
type: object
properties:
denom:
type: string
title: denom represents the denom of the vault
strategies:
type: array
items:
type: string
enum:
- STRATEGY_TYPE_UNSPECIFIED
- STRATEGY_TYPE_HARD
- STRATEGY_TYPE_SAVINGS
default: STRATEGY_TYPE_UNSPECIFIED
description: >-
StrategyType is the type of strategy that a vault uses
to optimize yields.
- STRATEGY_TYPE_UNSPECIFIED: STRATEGY_TYPE_UNSPECIFIED represents an unspecified or invalid strategy type.
- STRATEGY_TYPE_HARD: STRATEGY_TYPE_HARD represents the strategy that deposits assets in the Hard
module.
- STRATEGY_TYPE_SAVINGS: STRATEGY_TYPE_SAVINGS represents the strategy that deposits assets in the
Savings module.
description: VaultStrategy is the strategy used for this vault.
is_private_vault:
type: boolean
format: boolean
description: >-
IsPrivateVault is true if the vault only allows depositors
contained in
AllowedDepositors.
allowed_depositors:
type: array
items:
type: string
description: >-
AllowedDepositors is a list of addresses that are allowed
to deposit to
this vault if IsPrivateVault is true. Addresses not
contained in this list
are not allowed to deposit into this vault. If
IsPrivateVault is false,
this should be empty and ignored.
total_shares:
type: string
description: >-
TotalShares is the total amount of shares issued to
depositors.
total_value:
type: string
description: >-
TotalValue is the total value of denom coins supplied to
the vault if the
vault were to be liquidated.
description: VaultResponse is the response type for a vault.
title: vault represents the queried earn module vault
description: >-
QueryVaultResponse is the response type for the Query/Vault RPC
method.
default:
description: An unexpected error response
schema:
type: object
properties:
error:
type: string
code:
type: integer
format: int32
message:
type: string
details:
type: array
items:
type: object
properties:
type_url:
type: string
value:
type: string
format: byte
parameters:
- name: denom
description: vault filters vault by denom
@ -54527,6 +54626,70 @@ definitions:
description: >-
QueryParamsResponse defines the response type for querying x/earn
parameters.
kava.earn.v1beta1.QueryVaultResponse:
type: object
properties:
vault:
type: object
properties:
denom:
type: string
title: denom represents the denom of the vault
strategies:
type: array
items:
type: string
enum:
- STRATEGY_TYPE_UNSPECIFIED
- STRATEGY_TYPE_HARD
- STRATEGY_TYPE_SAVINGS
default: STRATEGY_TYPE_UNSPECIFIED
description: >-
StrategyType is the type of strategy that a vault uses to
optimize yields.
- STRATEGY_TYPE_UNSPECIFIED: STRATEGY_TYPE_UNSPECIFIED represents an unspecified or invalid strategy type.
- STRATEGY_TYPE_HARD: STRATEGY_TYPE_HARD represents the strategy that deposits assets in the Hard
module.
- STRATEGY_TYPE_SAVINGS: STRATEGY_TYPE_SAVINGS represents the strategy that deposits assets in the
Savings module.
description: VaultStrategy is the strategy used for this vault.
is_private_vault:
type: boolean
format: boolean
description: >-
IsPrivateVault is true if the vault only allows depositors
contained in
AllowedDepositors.
allowed_depositors:
type: array
items:
type: string
description: >-
AllowedDepositors is a list of addresses that are allowed to
deposit to
this vault if IsPrivateVault is true. Addresses not contained in
this list
are not allowed to deposit into this vault. If IsPrivateVault is
false,
this should be empty and ignored.
total_shares:
type: string
description: TotalShares is the total amount of shares issued to depositors.
total_value:
type: string
description: >-
TotalValue is the total value of denom coins supplied to the vault
if the
vault were to be liquidated.
description: VaultResponse is the response type for a vault.
title: vault represents the queried earn module vault
description: QueryVaultResponse is the response type for the Query/Vault RPC method.
kava.earn.v1beta1.QueryVaultsResponse:
type: object
properties:

View File

@ -199,6 +199,8 @@
- [QueryDepositsResponse](#kava.earn.v1beta1.QueryDepositsResponse)
- [QueryParamsRequest](#kava.earn.v1beta1.QueryParamsRequest)
- [QueryParamsResponse](#kava.earn.v1beta1.QueryParamsResponse)
- [QueryVaultRequest](#kava.earn.v1beta1.QueryVaultRequest)
- [QueryVaultResponse](#kava.earn.v1beta1.QueryVaultResponse)
- [QueryVaultsRequest](#kava.earn.v1beta1.QueryVaultsRequest)
- [QueryVaultsResponse](#kava.earn.v1beta1.QueryVaultsResponse)
- [VaultResponse](#kava.earn.v1beta1.VaultResponse)
@ -2967,7 +2969,7 @@ QueryDepositsRequest is the request type for the Query/Deposits RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `owner` | [string](#string) | | owner optionally filters deposits by owner |
| `depositor` | [string](#string) | | depositor optionally filters deposits by depositor |
| `denom` | [string](#string) | | denom optionally filters deposits by vault denom |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination defines an optional pagination for the request. |
@ -3017,10 +3019,10 @@ QueryParamsResponse defines the response type for querying x/earn parameters.
<a name="kava.earn.v1beta1.QueryVaultsRequest"></a>
<a name="kava.earn.v1beta1.QueryVaultRequest"></a>
### QueryVaultsRequest
QueryVaultsRequest is the request type for the Query/Vault RPC method.
### QueryVaultRequest
QueryVaultRequest is the request type for the Query/Vault RPC method.
| Field | Type | Label | Description |
@ -3032,6 +3034,31 @@ QueryVaultsRequest is the request type for the Query/Vault RPC method.
<a name="kava.earn.v1beta1.QueryVaultResponse"></a>
### QueryVaultResponse
QueryVaultResponse is the response type for the Query/Vault RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `vault` | [VaultResponse](#kava.earn.v1beta1.VaultResponse) | | vault represents the queried earn module vault |
<a name="kava.earn.v1beta1.QueryVaultsRequest"></a>
### QueryVaultsRequest
QueryVaultsRequest is the request type for the Query/Vaults RPC method.
<a name="kava.earn.v1beta1.QueryVaultsResponse"></a>
### QueryVaultsResponse
@ -3081,8 +3108,9 @@ Query defines the gRPC querier service for earn module
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `Params` | [QueryParamsRequest](#kava.earn.v1beta1.QueryParamsRequest) | [QueryParamsResponse](#kava.earn.v1beta1.QueryParamsResponse) | Params queries all parameters of the earn module. | GET|/kava/earn/v1beta1/params|
| `Vaults` | [QueryVaultsRequest](#kava.earn.v1beta1.QueryVaultsRequest) | [QueryVaultsResponse](#kava.earn.v1beta1.QueryVaultsResponse) | Vaults queries vaults based on vault denom | GET|/kava/earn/v1beta1/vaults/{denom}|
| `Deposits` | [QueryDepositsRequest](#kava.earn.v1beta1.QueryDepositsRequest) | [QueryDepositsResponse](#kava.earn.v1beta1.QueryDepositsResponse) | Deposits queries deposit details based on owner address and vault | GET|/kava/earn/v1beta1/deposits|
| `Vaults` | [QueryVaultsRequest](#kava.earn.v1beta1.QueryVaultsRequest) | [QueryVaultsResponse](#kava.earn.v1beta1.QueryVaultsResponse) | Vaults queries all vaults | GET|/kava/earn/v1beta1/vaults|
| `Vault` | [QueryVaultRequest](#kava.earn.v1beta1.QueryVaultRequest) | [QueryVaultResponse](#kava.earn.v1beta1.QueryVaultResponse) | Vault queries a single vault based on the vault denom | GET|/kava/earn/v1beta1/vaults/{denom}|
| `Deposits` | [QueryDepositsRequest](#kava.earn.v1beta1.QueryDepositsRequest) | [QueryDepositsResponse](#kava.earn.v1beta1.QueryDepositsResponse) | Deposits queries deposit details based on depositor address and vault | GET|/kava/earn/v1beta1/deposits|
<!-- end services -->

View File

@ -20,12 +20,17 @@ service Query {
option (google.api.http).get = "/kava/earn/v1beta1/params";
}
// Vaults queries vaults based on vault denom
// Vaults queries all vaults
rpc Vaults(QueryVaultsRequest) returns (QueryVaultsResponse) {
option (google.api.http).get = "/kava/earn/v1beta1/vaults";
}
// Vault queries a single vault based on the vault denom
rpc Vault(QueryVaultRequest) returns (QueryVaultResponse) {
option (google.api.http).get = "/kava/earn/v1beta1/vaults/{denom}";
}
// Deposits queries deposit details based on owner address and vault
// Deposits queries deposit details based on depositor address and vault
rpc Deposits(QueryDepositsRequest) returns (QueryDepositsResponse) {
option (google.api.http).get = "/kava/earn/v1beta1/deposits";
}
@ -40,11 +45,8 @@ message QueryParamsResponse {
Params params = 1 [(gogoproto.nullable) = false];
}
// QueryVaultsRequest is the request type for the Query/Vault RPC method.
message QueryVaultsRequest {
// vault filters vault by denom
string denom = 1;
}
// QueryVaultsRequest is the request type for the Query/Vaults RPC method.
message QueryVaultsRequest {}
// QueryVaultsResponse is the response type for the Query/Vaults RPC method.
message QueryVaultsResponse {
@ -52,6 +54,18 @@ message QueryVaultsResponse {
repeated VaultResponse vaults = 1 [(gogoproto.nullable) = false];
}
// QueryVaultRequest is the request type for the Query/Vault RPC method.
message QueryVaultRequest {
// vault filters vault by denom
string denom = 1;
}
// QueryVaultResponse is the response type for the Query/Vault RPC method.
message QueryVaultResponse {
// vault represents the queried earn module vault
VaultResponse vault = 1 [(gogoproto.nullable) = false];
}
// VaultResponse is the response type for a vault.
message VaultResponse {
// denom represents the denom of the vault
@ -84,8 +98,8 @@ message VaultResponse {
// QueryDepositsRequest is the request type for the Query/Deposits RPC method.
message QueryDepositsRequest {
// owner optionally filters deposits by owner
string owner = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// depositor optionally filters deposits by depositor
string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// denom optionally filters deposits by vault denom
string denom = 2;

View File

@ -32,6 +32,7 @@ func GetQueryCmd() *cobra.Command {
cmds := []*cobra.Command{
queryParamsCmd(),
queryVaultsCmd(),
queryVaultCmd(),
queryDepositsCmd(),
}
@ -71,13 +72,11 @@ func queryParamsCmd() *cobra.Command {
func queryVaultsCmd() *cobra.Command {
return &cobra.Command{
Use: "vaults",
Short: "get the earn vaults",
Long: "Get the earn module vaults.",
Args: cobra.MaximumNArgs(1),
Example: fmt.Sprintf(`%[1]s q %[2]s vaults
%[1]s q %[2]s vaults
%[1]s q %[2]s vaults usdx`, version.AppName, types.ModuleName),
Use: "vaults",
Short: "get all earn vaults",
Long: "Get all earn module vaults.",
Args: cobra.NoArgs,
Example: fmt.Sprintf(`%[1]s q %[2]s vaults`, version.AppName, types.ModuleName),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
@ -86,13 +85,34 @@ func queryVaultsCmd() *cobra.Command {
queryClient := types.NewQueryClient(clientCtx)
vaultDenom := ""
if len(args) > 1 {
vaultDenom = args[0]
req := types.NewQueryVaultsRequest()
res, err := queryClient.Vaults(context.Background(), req)
if err != nil {
return err
}
req := types.NewQueryVaultsRequest(vaultDenom)
res, err := queryClient.Vaults(context.Background(), req)
return clientCtx.PrintProto(res)
},
}
}
func queryVaultCmd() *cobra.Command {
return &cobra.Command{
Use: "vault",
Short: "get a earn vault",
Long: "Get a specific earn module vault by denom.",
Args: cobra.ExactArgs(1),
Example: fmt.Sprintf(`%[1]s q %[2]s vault usdx`, version.AppName, types.ModuleName),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)
req := types.NewQueryVaultRequest(args[0])
res, err := queryClient.Vault(context.Background(), req)
if err != nil {
return err
}

View File

@ -63,7 +63,8 @@ func (k *Keeper) Deposit(
return err
}
// Get VaultShareRecord for account, create if not exist
// Get VaultShareRecord for account, create if account has no deposits.
// This can still be found if the account has deposits for other vaults.
vaultShareRecord, found := k.GetVaultShareRecord(ctx, depositor)
if !found {
// Create a new empty VaultShareRecord with 0 supply
@ -75,6 +76,12 @@ func (k *Keeper) Deposit(
return fmt.Errorf("failed to convert assets to shares: %w", err)
}
isNew := vaultShareRecord.Shares.AmountOf(amount.Denom).IsZero()
if !isNew {
// If deposits for this vault already exists
k.BeforeVaultDepositModified(ctx, amount.Denom, depositor, vaultRecord.TotalShares.Amount)
}
// Increment VaultRecord total shares and account shares
vaultRecord.TotalShares = vaultRecord.TotalShares.Add(shares)
vaultShareRecord.Shares = vaultShareRecord.Shares.Add(shares)
@ -83,6 +90,11 @@ func (k *Keeper) Deposit(
k.SetVaultRecord(ctx, vaultRecord)
k.SetVaultShareRecord(ctx, vaultShareRecord)
if isNew {
// If first deposit in this vault
k.AfterVaultDepositCreated(ctx, amount.Denom, depositor, shares.Amount)
}
// Deposit to the strategy
if err := strategy.Deposit(ctx, amount); err != nil {
return err

View File

@ -167,3 +167,27 @@ func (suite *depositTestSuite) TestDeposit_PrivateVault() {
err = suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err, "private vault should allow deposits from allowed addresses")
}
func (suite *depositTestSuite) TestDeposit_bKava() {
vaultDenom := "bkava"
coinDenom := vaultDenom + "-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
startBalance := sdk.NewInt64Coin(coinDenom, 1000)
depositAmount := sdk.NewInt64Coin(coinDenom, 100)
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
// 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, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(
err,
"should be able to deposit bkava derivative denom in bkava vault",
)
}

View File

@ -51,50 +51,96 @@ func (s queryServer) Vaults(
sdkCtx := sdk.UnwrapSDKContext(ctx)
var queriedAllowedVaults types.AllowedVaults
if req.Denom != "" {
// Only 1 vault
allowedVault, found := s.keeper.GetAllowedVault(sdkCtx, req.Denom)
if !found {
return nil, status.Errorf(codes.NotFound, "vault not found with specified denom")
}
queriedAllowedVaults = types.AllowedVaults{allowedVault}
} else {
// All vaults
queriedAllowedVaults = s.keeper.GetAllowedVaults(sdkCtx)
}
vaults := []types.VaultResponse{}
for _, allowedVault := range queriedAllowedVaults {
vaultTotalShares, found := s.keeper.GetVaultTotalShares(sdkCtx, allowedVault.Denom)
var vaultRecordsErr error
// 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)
if !found {
// No supply yet, no error just zero
vaultTotalShares = types.NewVaultShare(allowedVault.Denom, sdk.ZeroDec())
vaultRecordsErr = fmt.Errorf("vault record not found for vault record denom %s", record.TotalShares.Denom)
}
totalValue, err := s.keeper.GetVaultTotalValue(sdkCtx, allowedVault.Denom)
totalValue, err := s.keeper.GetVaultTotalValue(sdkCtx, record.TotalShares.Denom)
if err != nil {
return nil, err
vaultRecordsErr = err
// Stop iterating if error
return true
}
vaults = append(vaults, types.VaultResponse{
Denom: allowedVault.Denom,
Denom: record.TotalShares.Denom,
Strategies: allowedVault.Strategies,
IsPrivateVault: allowedVault.IsPrivateVault,
AllowedDepositors: addressSliceToStringSlice(allowedVault.AllowedDepositors),
TotalShares: vaultTotalShares.Amount.String(),
TotalShares: record.TotalShares.Amount.String(),
TotalValue: totalValue.Amount,
})
return false
})
if vaultRecordsErr != nil {
return nil, vaultRecordsErr
}
// Does not include vaults that have no deposits, only iterates over vault
// records which exists only for those with deposits.
return &types.QueryVaultsResponse{
Vaults: vaults,
}, nil
}
// Vaults implements the gRPC service handler for querying x/earn vaults.
func (s queryServer) Vault(
ctx context.Context,
req *types.QueryVaultRequest,
) (*types.QueryVaultResponse, error) {
if req == nil {
return nil, status.Errorf(codes.InvalidArgument, "empty request")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
if req.Denom == "" {
return nil, status.Errorf(codes.InvalidArgument, "empty denom")
}
// Only 1 vault
allowedVault, found := s.keeper.GetAllowedVault(sdkCtx, req.Denom)
if !found {
return nil, status.Errorf(codes.NotFound, "vault not found with specified denom")
}
// Must be req.Denom and not allowedVault.Denom to get full "bkava" denom
vaultRecord, found := s.keeper.GetVaultRecord(sdkCtx, req.Denom)
if !found {
// No supply yet, no error just set it to zero
vaultRecord.TotalShares = types.NewVaultShare(req.Denom, sdk.ZeroDec())
}
totalValue, err := s.keeper.GetVaultTotalValue(sdkCtx, req.Denom)
if err != nil {
return nil, err
}
vault := types.VaultResponse{
// VaultRecord denom instead of AllowedVault.Denom for full bkava denom
Denom: vaultRecord.TotalShares.Denom,
Strategies: allowedVault.Strategies,
IsPrivateVault: allowedVault.IsPrivateVault,
AllowedDepositors: addressSliceToStringSlice(allowedVault.AllowedDepositors),
TotalShares: vaultRecord.TotalShares.Amount.String(),
TotalValue: totalValue.Amount,
}
return &types.QueryVaultResponse{
Vault: vault,
}, nil
}
// Deposits implements the gRPC service handler for querying x/earn deposits.
func (s queryServer) Deposits(
ctx context.Context,
@ -107,17 +153,17 @@ func (s queryServer) Deposits(
sdkCtx := sdk.UnwrapSDKContext(ctx)
// 1. Specific account and specific vault
if req.Owner != "" && req.Denom != "" {
if req.Depositor != "" && req.Denom != "" {
return s.getAccountVaultDeposit(sdkCtx, req)
}
// 2. All accounts, specific vault
if req.Owner == "" && req.Denom != "" {
if req.Depositor == "" && req.Denom != "" {
return s.getVaultAllDeposits(sdkCtx, req)
}
// 3. Specific account, all vaults
if req.Owner != "" && req.Denom == "" {
if req.Depositor != "" && req.Denom == "" {
return s.getAccountAllDeposits(sdkCtx, req)
}
@ -131,12 +177,12 @@ func (s queryServer) getAccountVaultDeposit(
ctx sdk.Context,
req *types.QueryDepositsRequest,
) (*types.QueryDepositsResponse, error) {
owner, err := sdk.AccAddressFromBech32(req.Owner)
depositor, err := sdk.AccAddressFromBech32(req.Depositor)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "Invalid address")
}
shareRecord, found := s.keeper.GetVaultShareRecord(ctx, owner)
shareRecord, found := s.keeper.GetVaultShareRecord(ctx, depositor)
if !found {
return nil, status.Error(codes.NotFound, "No deposit found for owner")
}
@ -145,7 +191,7 @@ func (s queryServer) getAccountVaultDeposit(
return nil, status.Error(codes.NotFound, fmt.Sprintf("No deposit for denom %s found for owner", req.Denom))
}
value, err := getAccountValue(ctx, s.keeper, owner, shareRecord.Shares)
value, err := getAccountValue(ctx, s.keeper, depositor, shareRecord.Shares)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
@ -153,7 +199,7 @@ func (s queryServer) getAccountVaultDeposit(
return &types.QueryDepositsResponse{
Deposits: []types.DepositResponse{
{
Depositor: owner.String(),
Depositor: depositor.String(),
Shares: shareRecord.Shares,
Value: value,
},
@ -225,25 +271,25 @@ func (s queryServer) getAccountAllDeposits(
ctx sdk.Context,
req *types.QueryDepositsRequest,
) (*types.QueryDepositsResponse, error) {
owner, err := sdk.AccAddressFromBech32(req.Owner)
depositor, err := sdk.AccAddressFromBech32(req.Depositor)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "Invalid address")
}
deposits := []types.DepositResponse{}
accountShare, found := s.keeper.GetVaultShareRecord(ctx, owner)
accountShare, found := s.keeper.GetVaultShareRecord(ctx, depositor)
if !found {
return nil, status.Error(codes.NotFound, "No deposit found for owner")
return nil, status.Error(codes.NotFound, "No deposit found for depositor")
}
value, err := getAccountValue(ctx, s.keeper, owner, accountShare.Shares)
value, err := getAccountValue(ctx, s.keeper, depositor, accountShare.Shares)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
deposits = append(deposits, types.DepositResponse{
Depositor: owner.String(),
Depositor: depositor.String(),
Shares: accountShare.Shares,
Value: value,
})

View File

@ -63,9 +63,8 @@ func (suite *grpcQueryTestSuite) TestVaults_ZeroSupply() {
suite.CreateVault("busd", types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
suite.Run("single", func() {
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest("usdx"))
res, err := suite.queryClient.Vault(context.Background(), types.NewQueryVaultRequest("usdx"))
suite.Require().NoError(err)
suite.Require().Len(res.Vaults, 1)
suite.Require().Equal(
types.VaultResponse{
Denom: "usdx",
@ -75,69 +74,66 @@ func (suite *grpcQueryTestSuite) TestVaults_ZeroSupply() {
TotalShares: sdk.NewDec(0).String(),
TotalValue: sdk.NewInt(0),
},
res.Vaults[0],
res.Vault,
)
})
suite.Run("all", func() {
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest(""))
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
suite.Require().NoError(err)
suite.Require().Len(res.Vaults, 2)
suite.Require().ElementsMatch(
[]types.VaultResponse{
{
Denom: "usdx",
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
IsPrivateVault: false,
AllowedDepositors: nil,
TotalShares: sdk.NewDec(0).String(),
TotalValue: sdk.NewInt(0),
},
{
Denom: "busd",
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
IsPrivateVault: false,
AllowedDepositors: nil,
TotalShares: sdk.NewDec(0).String(),
TotalValue: sdk.NewInt(0),
},
},
res.Vaults,
)
suite.Require().Empty(res.Vaults)
})
}
func (suite *grpcQueryTestSuite) TestVaults_WithSupply() {
vaultDenom := "usdx"
vault2Denom := "bkava-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
acc := suite.CreateAccount(sdk.NewCoins(
sdk.NewInt64Coin(vaultDenom, 1000),
sdk.NewInt64Coin(vault2Denom, 1000),
), 0)
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest("usdx"))
err = suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), deposit2Amount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
suite.Require().Len(res.Vaults, 1)
suite.Require().Equal(
types.VaultResponse{
Denom: "usdx",
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
IsPrivateVault: false,
AllowedDepositors: nil,
TotalShares: depositAmount.Amount.ToDec().String(),
TotalValue: depositAmount.Amount,
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
suite.Require().NoError(err)
suite.Require().Len(res.Vaults, 2)
suite.Require().ElementsMatch(
[]types.VaultResponse{
{
Denom: vaultDenom,
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
IsPrivateVault: false,
AllowedDepositors: nil,
TotalShares: depositAmount.Amount.ToDec().String(),
TotalValue: depositAmount.Amount,
},
{
Denom: vault2Denom,
Strategies: []types.StrategyType{types.STRATEGY_TYPE_SAVINGS},
IsPrivateVault: false,
AllowedDepositors: nil,
TotalShares: deposit2Amount.Amount.ToDec().String(),
TotalValue: deposit2Amount.Amount,
},
},
res.Vaults[0],
res.Vaults,
)
}
func (suite *grpcQueryTestSuite) TestVaults_NotFound() {
_, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest("usdx"))
func (suite *grpcQueryTestSuite) TestVault_NotFound() {
_, err := suite.queryClient.Vault(context.Background(), types.NewQueryVaultRequest("usdx"))
suite.Require().Error(err)
suite.Require().ErrorIs(err, status.Errorf(codes.NotFound, "vault not found with specified denom"))
}
@ -145,12 +141,12 @@ func (suite *grpcQueryTestSuite) TestVaults_NotFound() {
func (suite *grpcQueryTestSuite) TestDeposits() {
vault1Denom := "usdx"
vault2Denom := "busd"
vault3Denom := "kava"
vault3Denom := "bkava-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
// 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(vault3Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
startBalance := sdk.NewCoins(
sdk.NewInt64Coin(vault1Denom, 1000),
@ -175,7 +171,7 @@ func (suite *grpcQueryTestSuite) TestDeposits() {
err = suite.Keeper.Deposit(suite.Ctx, acc2, deposit1Amount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
err = suite.Keeper.Deposit(suite.Ctx, acc2, deposit3Amount, types.STRATEGY_TYPE_HARD)
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() {
@ -317,3 +313,46 @@ func (suite *grpcQueryTestSuite) TestDeposits_InvalidAddress() {
suite.Require().Error(err)
suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "Invalid address"))
}
func (suite *grpcQueryTestSuite) TestVault_bKava() {
vaultDenom := "bkava"
coinDenom := vaultDenom + "-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
startBalance := sdk.NewInt64Coin(coinDenom, 1000)
depositAmount := sdk.NewInt64Coin(coinDenom, 100)
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
// 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, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(
err,
"should be able to deposit bkava derivative denom in bkava vault",
)
res, err := suite.queryClient.Vault(
context.Background(),
types.NewQueryVaultRequest(coinDenom),
)
suite.Require().NoError(err)
suite.Require().Equal(
types.VaultResponse{
Denom: coinDenom,
Strategies: types.StrategyTypes{
types.STRATEGY_TYPE_SAVINGS,
},
IsPrivateVault: false,
AllowedDepositors: []string(nil),
TotalShares: "100.000000000000000000",
TotalValue: sdk.NewInt(100),
},
res.Vault,
)
}

34
x/earn/keeper/hooks.go Normal file
View File

@ -0,0 +1,34 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/earn/types"
)
// Implements EarnHooks interface
var _ types.EarnHooks = Keeper{}
// AfterVaultDepositCreated - call hook if registered
func (k Keeper) AfterVaultDepositCreated(
ctx sdk.Context,
vaultDenom string,
depositor sdk.AccAddress,
sharesOwned sdk.Dec,
) {
if k.hooks != nil {
k.hooks.AfterVaultDepositCreated(ctx, vaultDenom, depositor, sharesOwned)
}
}
// BeforeVaultDepositModified - call hook if registered
func (k Keeper) BeforeVaultDepositModified(
ctx sdk.Context,
vaultDenom string,
depositor sdk.AccAddress,
sharesOwned sdk.Dec,
) {
if k.hooks != nil {
k.hooks.BeforeVaultDepositModified(ctx, vaultDenom, depositor, sharesOwned)
}
}

322
x/earn/keeper/hooks_test.go Normal file
View File

@ -0,0 +1,322 @@
package keeper_test
import (
"testing"
"github.com/kava-labs/kava/x/earn/testutil"
"github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/earn/types/mocks"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/suite"
)
type hookTestSuite struct {
testutil.Suite
}
func (suite *hookTestSuite) SetupTest() {
suite.Suite.SetupTest()
suite.Keeper.SetParams(suite.Ctx, types.DefaultParams())
}
func TestHookTestSuite(t *testing.T) {
suite.Run(t, new(hookTestSuite))
}
func (suite *hookTestSuite) TestHooks_DepositAndWithdraw() {
suite.Keeper.ClearHooks()
earnHooks := &mocks.EarnHooks{}
suite.Keeper.SetHooks(earnHooks)
vault1Denom := "usdx"
vault2Denom := "ukava"
deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100)
deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100)
suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(
sdk.NewInt64Coin(vault1Denom, 1000),
sdk.NewInt64Coin(vault2Denom, 1000),
), 0)
// first deposit creates vault - calls AfterVaultDepositCreated with initial shares
// shares are 1:1
earnHooks.On(
"AfterVaultDepositCreated",
suite.Ctx,
deposit1Amount.Denom,
acc.GetAddress(),
deposit1Amount.Amount.ToDec(),
).Once()
err := suite.Keeper.Deposit(
suite.Ctx,
acc.GetAddress(),
deposit1Amount,
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// second deposit adds to vault - calls BeforeVaultDepositModified
// shares given are the initial shares, not new the shares added to the vault
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit1Amount.Denom,
acc.GetAddress(),
deposit1Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc.GetAddress(),
deposit1Amount,
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// get the shares from the store from the last deposit
shareRecord, found := suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc.GetAddress(),
)
suite.Require().True(found)
// third deposit adds to vault - calls BeforeVaultDepositModified
// shares given are the shares added in previous deposit, not the shares added to the vault now
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit1Amount.Denom,
acc.GetAddress(),
shareRecord.AmountOf(deposit1Amount.Denom),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc.GetAddress(),
deposit1Amount,
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// new deposit denom into vault creates the deposit and calls AfterVaultDepositCreated
earnHooks.On(
"AfterVaultDepositCreated",
suite.Ctx,
deposit2Amount.Denom,
acc.GetAddress(),
deposit2Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc.GetAddress(),
deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// second deposit into vault calls BeforeVaultDepositModified with initial shares given
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit2Amount.Denom,
acc.GetAddress(),
deposit2Amount.Amount.ToDec(),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc.GetAddress(),
deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// get the shares from the store from the last deposit
shareRecord, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc.GetAddress(),
)
suite.Require().True(found)
// third deposit into vault calls BeforeVaultDepositModified with shares from last deposit
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit2Amount.Denom,
acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom),
).Once()
err = suite.Keeper.Deposit(
suite.Ctx,
acc.GetAddress(),
deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// ------------------------------------------------------------
// test hooks with a full withdraw of all shares deposit 1 denom
shareRecord, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc.GetAddress(),
)
suite.Require().True(found)
// all shares given to BeforeVaultDepositModified
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit1Amount.Denom,
acc.GetAddress(),
shareRecord.AmountOf(deposit1Amount.Denom),
).Once()
err = suite.Keeper.Withdraw(
suite.Ctx,
acc.GetAddress(),
// 3 deposits, multiply original deposit amount by 3
sdk.NewCoin(deposit1Amount.Denom, deposit1Amount.Amount.MulRaw(3)),
types.STRATEGY_TYPE_HARD,
)
suite.Require().NoError(err)
// test hooks on partial withdraw
shareRecord, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc.GetAddress(),
)
suite.Require().True(found)
// all shares given to before deposit modified even with partial withdraw
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit2Amount.Denom,
acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom),
).Once()
err = suite.Keeper.Withdraw(
suite.Ctx,
acc.GetAddress(),
deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// test hooks on second partial withdraw
shareRecord, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc.GetAddress(),
)
suite.Require().True(found)
// all shares given to before deposit modified even with partial withdraw
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit2Amount.Denom,
acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom),
).Once()
err = suite.Keeper.Withdraw(
suite.Ctx,
acc.GetAddress(),
deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
// test hooks withdraw all remaining shares
shareRecord, found = suite.Keeper.GetVaultAccountShares(
suite.Ctx,
acc.GetAddress(),
)
suite.Require().True(found)
// all shares given to before deposit modified even with partial withdraw
earnHooks.On(
"BeforeVaultDepositModified",
suite.Ctx,
deposit2Amount.Denom,
acc.GetAddress(),
shareRecord.AmountOf(deposit2Amount.Denom),
).Once()
err = suite.Keeper.Withdraw(
suite.Ctx,
acc.GetAddress(),
deposit2Amount,
types.STRATEGY_TYPE_SAVINGS,
)
suite.Require().NoError(err)
earnHooks.AssertExpectations(suite.T())
}
func (suite *hookTestSuite) TestHooks_NoPanicsOnNilHooks() {
suite.Keeper.ClearHooks()
vaultDenom := "usdx"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
withdrawAmount := sdk.NewInt64Coin(vaultDenom, 100)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
// AfterVaultDepositModified should not panic if no hooks are registered
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
// BeforeVaultDepositModified should not panic if no hooks are registered
err = suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
// BeforeVaultDepositModified should not panic if no hooks are registered
err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), withdrawAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
}
func (suite *hookTestSuite) TestHooks_HookOrdering() {
suite.Keeper.ClearHooks()
earnHooks := &mocks.EarnHooks{}
suite.Keeper.SetHooks(earnHooks)
vaultDenom := "usdx"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
earnHooks.On("AfterVaultDepositCreated", suite.Ctx, depositAmount.Denom, acc.GetAddress(), depositAmount.Amount.ToDec()).
Run(func(args mock.Arguments) {
shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc.GetAddress())
suite.Require().True(found, "expected after hook to be called after shares are updated")
suite.Require().Equal(depositAmount.Amount.ToDec(), shares.AmountOf(depositAmount.Denom))
})
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
earnHooks.On("BeforeVaultDepositModified", suite.Ctx, depositAmount.Denom, acc.GetAddress(), depositAmount.Amount.ToDec()).
Run(func(args mock.Arguments) {
shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc.GetAddress())
suite.Require().True(found, "expected after hook to be called after shares are updated")
suite.Require().Equal(depositAmount.Amount.ToDec(), shares.AmountOf(depositAmount.Denom))
})
err = suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
existingShares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc.GetAddress())
suite.Require().True(found)
earnHooks.On("BeforeVaultDepositModified", suite.Ctx, depositAmount.Denom, acc.GetAddress(), existingShares.AmountOf(depositAmount.Denom)).
Run(func(args mock.Arguments) {
shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc.GetAddress())
suite.Require().True(found, "expected after hook to be called after shares are updated")
suite.Require().Equal(depositAmount.Amount.MulRaw(2).ToDec(), shares.AmountOf(depositAmount.Denom))
})
err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
}

View File

@ -1,9 +1,115 @@
package keeper
import (
"github.com/kava-labs/kava/x/earn/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// RegisterInvariants registers the earn module invariants
// RegisterInvariants registers the swap module invariants
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, "vault-records", VaultRecordsInvariant(k))
ir.RegisterRoute(types.ModuleName, "share-records", ShareRecordsInvariant(k))
ir.RegisterRoute(types.ModuleName, "vault-shares", VaultSharesInvariant(k))
}
// AllInvariants runs all invariants of the swap module
func AllInvariants(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
if res, stop := VaultRecordsInvariant(k)(ctx); stop {
return res, stop
}
if res, stop := ShareRecordsInvariant(k)(ctx); stop {
return res, stop
}
res, stop := VaultSharesInvariant(k)(ctx)
return res, stop
}
}
// VaultRecordsInvariant iterates all vault records and asserts that they are valid
func VaultRecordsInvariant(k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(types.ModuleName, "validate vault records broken", "vault record invalid")
return func(ctx sdk.Context) (string, bool) {
k.IterateVaultRecords(ctx, func(record types.VaultRecord) bool {
if err := record.Validate(); err != nil {
broken = true
return true
}
return false
})
return message, broken
}
}
// ShareRecordsInvariant iterates all share records and asserts that they are valid
func ShareRecordsInvariant(k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(types.ModuleName, "validate share records broken", "share record invalid")
return func(ctx sdk.Context) (string, bool) {
k.IterateVaultShareRecords(ctx, func(record types.VaultShareRecord) bool {
if err := record.Validate(); err != nil {
broken = true
return true
}
return false
})
return message, broken
}
}
type vaultShares struct {
totalShares types.VaultShare
totalSharesOwned types.VaultShare
}
// VaultSharesInvariant iterates all vaults and shares and ensures the total vault shares match the sum of depositor shares
func VaultSharesInvariant(k Keeper) sdk.Invariant {
broken := false
message := sdk.FormatInvariant(types.ModuleName, "vault shares broken", "vault shares do not match depositor shares")
return func(ctx sdk.Context) (string, bool) {
totalShares := make(map[string]vaultShares)
k.IterateVaultRecords(ctx, func(record types.VaultRecord) bool {
totalShares[record.TotalShares.Denom] = vaultShares{
totalShares: record.TotalShares,
totalSharesOwned: types.NewVaultShare(record.TotalShares.Denom, sdk.ZeroDec()),
}
return false
})
k.IterateVaultShareRecords(ctx, func(sr types.VaultShareRecord) bool {
for _, share := range sr.Shares {
if shares, found := totalShares[share.Denom]; found {
shares.totalSharesOwned = shares.totalSharesOwned.Add(share)
totalShares[share.Denom] = shares
} else {
totalShares[share.Denom] = vaultShares{
totalShares: types.NewVaultShare(share.Denom, sdk.ZeroDec()),
totalSharesOwned: share,
}
}
}
return false
})
for _, share := range totalShares {
if !share.totalShares.Amount.Equal(share.totalSharesOwned.Amount) {
broken = true
break
}
}
return message, broken
}
}

View File

@ -0,0 +1,182 @@
package keeper_test
import (
"testing"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/earn/keeper"
"github.com/kava-labs/kava/x/earn/testutil"
"github.com/kava-labs/kava/x/earn/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
)
type invariantTestSuite struct {
testutil.Suite
invariants map[string]map[string]sdk.Invariant
addrs []sdk.AccAddress
}
func TestInvariantTestSuite(t *testing.T) {
suite.Run(t, new(invariantTestSuite))
}
func (suite *invariantTestSuite) SetupTest() {
suite.Suite.SetupTest()
suite.invariants = make(map[string]map[string]sdk.Invariant)
keeper.RegisterInvariants(suite, suite.Keeper)
_, addrs := app.GeneratePrivKeyAddressPairs(4)
suite.addrs = addrs
}
func (suite *invariantTestSuite) SetupValidState() {
suite.Keeper.SetVaultRecord(suite.Ctx, types.NewVaultRecord(
"usdx",
sdk.MustNewDecFromStr("100"),
))
suite.Keeper.SetVaultRecord(suite.Ctx, types.NewVaultRecord(
"ukava",
sdk.MustNewDecFromStr("250.123456"),
))
vaultShare1 := types.NewVaultShareRecord(
suite.addrs[0],
types.NewVaultShares(
types.NewVaultShare("usdx", sdk.MustNewDecFromStr("50")),
types.NewVaultShare("ukava", sdk.MustNewDecFromStr("105.123")),
),
)
vaultShare2 := types.NewVaultShareRecord(
suite.addrs[1],
types.NewVaultShares(
types.NewVaultShare("usdx", sdk.MustNewDecFromStr("50")),
types.NewVaultShare("ukava", sdk.MustNewDecFromStr("145.000456")),
),
)
suite.Require().NoError(vaultShare1.Validate())
suite.Require().NoError(vaultShare2.Validate())
suite.Keeper.SetVaultShareRecord(suite.Ctx, vaultShare1)
suite.Keeper.SetVaultShareRecord(suite.Ctx, vaultShare2)
}
func (suite *invariantTestSuite) RegisterRoute(moduleName string, route string, invariant sdk.Invariant) {
_, exists := suite.invariants[moduleName]
if !exists {
suite.invariants[moduleName] = make(map[string]sdk.Invariant)
}
suite.invariants[moduleName][route] = invariant
}
func (suite *invariantTestSuite) runInvariant(route string, invariant func(k keeper.Keeper) sdk.Invariant) (string, bool) {
ctx := suite.Ctx
registeredInvariant := suite.invariants[types.ModuleName][route]
suite.Require().NotNil(registeredInvariant)
// direct call
dMessage, dBroken := invariant(suite.Keeper)(ctx)
// registered call
rMessage, rBroken := registeredInvariant(ctx)
// all call
aMessage, aBroken := keeper.AllInvariants(suite.Keeper)(ctx)
// require matching values for direct call and registered call
suite.Require().Equal(dMessage, rMessage, "expected registered invariant message to match")
suite.Require().Equal(dBroken, rBroken, "expected registered invariant broken to match")
// require matching values for direct call and all invariants call if broken
suite.Require().Equalf(dBroken, aBroken, "expected all invariant broken to match, direct %v != all %v", dBroken, aBroken)
if dBroken {
suite.Require().Equal(dMessage, aMessage, "expected all invariant message to match")
}
// return message, broken
return dMessage, dBroken
}
func (suite *invariantTestSuite) TestVaultRecordsInvariant() {
// default state is valid
message, broken := suite.runInvariant("vault-records", keeper.VaultRecordsInvariant)
suite.Equal("earn: validate vault records broken invariant\nvault record invalid\n", message)
suite.Equal(false, broken)
suite.SetupValidState()
message, broken = suite.runInvariant("vault-records", keeper.VaultRecordsInvariant)
suite.Equal("earn: validate vault records broken invariant\nvault record invalid\n", message)
suite.Equal(false, broken)
// broken with invalid vault record
suite.Keeper.SetVaultRecord(suite.Ctx, types.VaultRecord{
TotalShares: types.VaultShare{
Denom: "invalid denom",
Amount: sdk.MustNewDecFromStr("101"),
},
})
message, broken = suite.runInvariant("vault-records", keeper.VaultRecordsInvariant)
suite.Equal("earn: validate vault records broken invariant\nvault record invalid\n", message)
suite.Equal(true, broken)
}
func (suite *invariantTestSuite) TestShareRecordsInvariant() {
message, broken := suite.runInvariant("share-records", keeper.ShareRecordsInvariant)
suite.Equal("earn: validate share records broken invariant\nshare record invalid\n", message)
suite.Equal(false, broken)
suite.SetupValidState()
message, broken = suite.runInvariant("share-records", keeper.ShareRecordsInvariant)
suite.Equal("earn: validate share records broken invariant\nshare record invalid\n", message)
suite.Equal(false, broken)
// broken with invalid share record
suite.Keeper.SetVaultShareRecord(suite.Ctx, types.NewVaultShareRecord(
suite.addrs[0],
// Directly create vaultshares instead of NewVaultShares() to avoid sanitization
types.VaultShares{
types.NewVaultShare("ukava", sdk.MustNewDecFromStr("50")),
types.NewVaultShare("ukava", sdk.MustNewDecFromStr("105.123")),
},
))
message, broken = suite.runInvariant("share-records", keeper.ShareRecordsInvariant)
suite.Equal("earn: validate share records broken invariant\nshare record invalid\n", message)
suite.Equal(true, broken)
}
func (suite *invariantTestSuite) TestVaultSharesInvariant() {
message, broken := suite.runInvariant("vault-shares", keeper.VaultSharesInvariant)
suite.Equal("earn: vault shares broken invariant\nvault shares do not match depositor shares\n", message)
suite.Equal(false, broken)
suite.SetupValidState()
message, broken = suite.runInvariant("vault-shares", keeper.VaultSharesInvariant)
suite.Equal("earn: vault shares broken invariant\nvault shares do not match depositor shares\n", message)
suite.Equal(false, broken)
// broken when total shares are greater than depositor shares
suite.Keeper.SetVaultRecord(suite.Ctx, types.NewVaultRecord(
"usdx",
sdk.MustNewDecFromStr("101"),
))
message, broken = suite.runInvariant("vault-shares", keeper.VaultSharesInvariant)
suite.Equal("earn: vault shares broken invariant\nvault shares do not match depositor shares\n", message)
suite.Equal(true, broken)
// broken when total shares are less than the depositor shares
suite.Keeper.SetVaultRecord(suite.Ctx, types.NewVaultRecord(
"usdx",
sdk.MustNewDecFromStr("99.999"),
))
message, broken = suite.runInvariant("vault-shares", keeper.VaultSharesInvariant)
suite.Equal("earn: vault shares broken invariant\nvault shares do not match depositor shares\n", message)
suite.Equal(true, broken)
// broken when vault record is missing
suite.Keeper.DeleteVaultRecord(suite.Ctx, "usdx")
message, broken = suite.runInvariant("vault-shares", keeper.VaultSharesInvariant)
suite.Equal("earn: vault shares broken invariant\nvault shares do not match depositor shares\n", message)
suite.Equal(true, broken)
}

View File

@ -13,6 +13,7 @@ type Keeper struct {
key sdk.StoreKey
cdc codec.Codec
paramSubspace paramtypes.Subspace
hooks types.EarnHooks
accountKeeper types.AccountKeeper
bankKeeper types.BankKeeper
@ -45,3 +46,17 @@ func NewKeeper(
savingsKeeper: savingsKeeper,
}
}
// SetHooks adds hooks to the keeper.
func (k *Keeper) SetHooks(sh types.EarnHooks) *Keeper {
if k.hooks != nil {
panic("cannot set earn hooks twice")
}
k.hooks = sh
return k
}
// ClearHooks clears the hooks on the keeper
func (k *Keeper) ClearHooks() {
k.hooks = nil
}

View File

@ -1,11 +1,18 @@
package keeper
import (
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/earn/types"
)
const (
bkavaDenom = "bkava"
bkavaPrefix = bkavaDenom + "-"
)
// GetParams returns the params from the store
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
var p types.Params
@ -24,9 +31,12 @@ func (k Keeper) GetAllowedVaults(ctx sdk.Context) types.AllowedVaults {
return k.GetParams(ctx).AllowedVaults
}
// GetAllowedVault returns a single vault from the module params specified by
// the denom.
func (k Keeper) GetAllowedVault(ctx sdk.Context, vaultDenom string) (types.AllowedVault, bool) {
// getAllowedVaultRaw returns a single vault from the module params specified
// by the denom.
func (k Keeper) getAllowedVaultRaw(
ctx sdk.Context,
vaultDenom string,
) (types.AllowedVault, bool) {
for _, allowedVault := range k.GetAllowedVaults(ctx) {
if allowedVault.Denom == vaultDenom {
return allowedVault, true
@ -35,3 +45,18 @@ func (k Keeper) GetAllowedVault(ctx sdk.Context, vaultDenom string) (types.Allow
return types.AllowedVault{}, false
}
// GetAllowedVault returns the AllowedVault that corresponds to the
// given denom. If the denom starts with "bkava-" where it will return the
// "bkava" AllowedVault. Otherwise, it will return the exact match for the
// corresponding AllowedVault denom.
func (k *Keeper) GetAllowedVault(
ctx sdk.Context,
vaultDenom string,
) (types.AllowedVault, bool) {
if strings.HasPrefix(vaultDenom, bkavaPrefix) {
return k.getAllowedVaultRaw(ctx, bkavaDenom)
}
return k.getAllowedVaultRaw(ctx, vaultDenom)
}

View File

@ -12,20 +12,18 @@ type Strategy interface {
// GetStrategyType returns the strategy type
GetStrategyType() types.StrategyType
// GetEstimatedTotalAssets returns the estimated total assets denominated in
// GetDenom() of this strategy. This is the value if the strategy were to
// liquidate all assets.
// GetEstimatedTotalAssets returns the estimated total assets of the
// strategy with the specified denom. This is the value if the strategy were
// to liquidate all assets.
//
// **Note:** This may not reflect the true value as it may become outdated
// from market changes.
GetEstimatedTotalAssets(ctx sdk.Context, denom string) (sdk.Coin, error)
// Deposit the specified amount of coins into this strategy. The amount
// must be denominated in GetDenom().
// Deposit the specified amount of coins into this strategy.
Deposit(ctx sdk.Context, amount sdk.Coin) error
// Withdraw the specified amount of coins from this strategy. The amount
// must be denominated in GetDenom().
// Withdraw the specified amount of coins from this strategy.
Withdraw(ctx sdk.Context, amount sdk.Coin) error
}
@ -35,7 +33,7 @@ func (k *Keeper) GetStrategy(strategyType types.StrategyType) (Strategy, error)
case types.STRATEGY_TYPE_HARD:
return (*HardStrategy)(k), nil
case types.STRATEGY_TYPE_SAVINGS:
panic("unimplemented")
return (*SavingsStrategy)(k), nil
default:
return nil, fmt.Errorf("unknown strategy type: %s", strategyType)
}

View File

@ -10,14 +10,13 @@ type HardStrategy Keeper
var _ Strategy = (*HardStrategy)(nil)
// GetStrategyType returns the strategy type
func (s *HardStrategy) GetStrategyType() types.StrategyType {
return types.STRATEGY_TYPE_HARD
}
func (s *HardStrategy) IsDenomSupported(denom string) bool {
return denom == "usdx"
}
// GetEstimatedTotalAssets returns the current value of all assets deposited
// in hard.
func (s *HardStrategy) GetEstimatedTotalAssets(ctx sdk.Context, denom string) (sdk.Coin, error) {
macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
deposit, found := s.hardKeeper.GetSyncedDeposit(ctx, macc.GetAddress())
@ -37,11 +36,13 @@ func (s *HardStrategy) GetEstimatedTotalAssets(ctx sdk.Context, denom string) (s
return sdk.NewCoin(denom, sdk.ZeroInt()), nil
}
// Deposit deposits the specified amount of coins into hard.
func (s *HardStrategy) Deposit(ctx sdk.Context, amount sdk.Coin) error {
macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
return s.hardKeeper.Deposit(ctx, macc.GetAddress(), sdk.NewCoins(amount))
}
// Withdraw withdraws the specified amount of coins from hard.
func (s *HardStrategy) Withdraw(ctx sdk.Context, amount sdk.Coin) error {
macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
return s.hardKeeper.Withdraw(ctx, macc.GetAddress(), sdk.NewCoins(amount))

View File

@ -250,9 +250,12 @@ func (suite *strategyHardTestSuite) TestWithdraw_WithAccumulatedHard() {
suite.Require().NoError(err)
// Direct hard deposit from module account to increase vault value
suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 20)))
err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 20)))
suite.Require().NoError(err)
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 20)))
err = suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 20)))
suite.Require().NoError(err)
// Query account value
accValue, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc)
@ -293,7 +296,8 @@ func (suite *strategyHardTestSuite) TestAccountShares() {
vaultDenom := "usdx"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1000)))
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1000)))
suite.Require().NoError(err)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
@ -302,7 +306,7 @@ func (suite *strategyHardTestSuite) TestAccountShares() {
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
// 1. acc1 deposit 100
err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
@ -344,7 +348,8 @@ func (suite *strategyHardTestSuite) TestAccountShares() {
// Hard deposit again from module account to triple original value
// 210 -> 300
suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 90)))
err = suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 90)))
suite.Require().NoError(err)
// Deposit again from acc1
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
@ -368,7 +373,8 @@ func (suite *strategyHardTestSuite) TestWithdraw_AccumulatedAmount() {
vaultDenom := "usdx"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1000)))
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1000)))
suite.Require().NoError(err)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
@ -377,7 +383,7 @@ func (suite *strategyHardTestSuite) TestWithdraw_AccumulatedAmount() {
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
// 1. acc1 deposit 100
err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
// acc2 deposit 100, just to make sure other deposits do not affect acc1
@ -406,7 +412,8 @@ func (suite *strategyHardTestSuite) TestWithdraw_AccumulatedTruncated() {
vaultDenom := "usdx"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1000)))
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1000)))
suite.Require().NoError(err)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
@ -415,7 +422,7 @@ func (suite *strategyHardTestSuite) TestWithdraw_AccumulatedTruncated() {
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
// 1. acc1 deposit 100
err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
// acc2 deposit 100, just to make sure other deposits do not affect acc1
@ -451,7 +458,8 @@ func (suite *strategyHardTestSuite) TestWithdraw_ExpensiveShares() {
vaultDenom := "usdx"
startBalance := sdk.NewInt64Coin(vaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 2000)))
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 2000)))
suite.Require().NoError(err)
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
@ -459,7 +467,7 @@ func (suite *strategyHardTestSuite) TestWithdraw_ExpensiveShares() {
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
// 1. acc1 deposit 100
err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_HARD)
suite.Require().NoError(err)
acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)

View File

@ -0,0 +1,49 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/earn/types"
)
// SavingsStrategy defines the strategy that deposits assets to x/savings
type SavingsStrategy Keeper
var _ Strategy = (*SavingsStrategy)(nil)
// GetStrategyType returns the strategy type
func (s *SavingsStrategy) GetStrategyType() types.StrategyType {
return types.STRATEGY_TYPE_SAVINGS
}
// GetEstimatedTotalAssets returns the current value of all assets deposited
// in savings.
func (s *SavingsStrategy) GetEstimatedTotalAssets(ctx sdk.Context, denom string) (sdk.Coin, error) {
macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
deposit, found := s.savingsKeeper.GetDeposit(ctx, macc.GetAddress())
if !found {
// Return 0 if no deposit exists for module account
return sdk.NewCoin(denom, sdk.ZeroInt()), nil
}
// Only return the deposit for the vault denom.
for _, coin := range deposit.Amount {
if coin.Denom == denom {
return coin, nil
}
}
// Return 0 if no deposit exists for the vault denom
return sdk.NewCoin(denom, sdk.ZeroInt()), nil
}
// Deposit deposits the specified amount of coins into savings.
func (s *SavingsStrategy) Deposit(ctx sdk.Context, amount sdk.Coin) error {
macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
return s.savingsKeeper.Deposit(ctx, macc.GetAddress(), sdk.NewCoins(amount))
}
// Withdraw withdraws the specified amount of coins from savings.
func (s *SavingsStrategy) Withdraw(ctx sdk.Context, amount sdk.Coin) error {
macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName)
return s.savingsKeeper.Withdraw(ctx, macc.GetAddress(), sdk.NewCoins(amount))
}

View File

@ -0,0 +1,486 @@
package keeper_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/earn/testutil"
"github.com/kava-labs/kava/x/earn/types"
"github.com/stretchr/testify/suite"
)
const savingsVaultDenom = "ukava"
type strategySavingsTestSuite struct {
testutil.Suite
}
func (suite *strategySavingsTestSuite) SetupTest() {
suite.Suite.SetupTest()
suite.Keeper.SetParams(suite.Ctx, types.DefaultParams())
}
func TestStrategySavingsTestSuite(t *testing.T) {
suite.Run(t, new(strategySavingsTestSuite))
}
func (suite *strategySavingsTestSuite) TestGetStrategyType() {
strategy, err := suite.Keeper.GetStrategy(types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
suite.Equal(types.STRATEGY_TYPE_SAVINGS, strategy.GetStrategyType())
}
func (suite *strategySavingsTestSuite) TestDeposit_SingleAcc() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
suite.SavingsDepositAmountEqual(sdk.NewCoins(depositAmount))
suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount))
suite.VaultTotalSharesEqual(types.NewVaultShares(
types.NewVaultShare(depositAmount.Denom, depositAmount.Amount.ToDec()),
))
// Query vault total
totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, savingsVaultDenom)
suite.Require().NoError(err)
suite.Equal(depositAmount, totalValue)
}
func (suite *strategySavingsTestSuite) TestDeposit_SingleAcc_MultipleDeposits() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Second deposit
err = suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
expectedVaultBalance := depositAmount.Add(depositAmount)
suite.SavingsDepositAmountEqual(sdk.NewCoins(expectedVaultBalance))
suite.VaultTotalValuesEqual(sdk.NewCoins(expectedVaultBalance))
suite.VaultTotalSharesEqual(types.NewVaultShares(
types.NewVaultShare(expectedVaultBalance.Denom, expectedVaultBalance.Amount.ToDec()),
))
// Query vault total
totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, savingsVaultDenom)
suite.Require().NoError(err)
suite.Equal(depositAmount.Add(depositAmount), totalValue)
}
func (suite *strategySavingsTestSuite) TestDeposit_MultipleAcc_MultipleDeposits() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
expectedTotalValue := sdk.NewCoin(savingsVaultDenom, depositAmount.Amount.MulRaw(4))
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
// 2 deposits each account
for i := 0; i < 2; i++ {
// Deposit from acc1
err := suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Deposit from acc2
err = suite.Keeper.Deposit(suite.Ctx, acc2.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
}
suite.SavingsDepositAmountEqual(sdk.NewCoins(expectedTotalValue))
suite.VaultTotalValuesEqual(sdk.NewCoins(expectedTotalValue))
suite.VaultTotalSharesEqual(types.NewVaultShares(
types.NewVaultShare(expectedTotalValue.Denom, expectedTotalValue.Amount.ToDec()),
))
// Query vault total
totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, savingsVaultDenom)
suite.Require().NoError(err)
suite.Equal(expectedTotalValue, totalValue)
}
func (suite *strategySavingsTestSuite) TestGetVaultTotalValue_Empty() {
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Query vault total
totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, savingsVaultDenom)
suite.Require().NoError(err)
suite.Equal(sdk.NewCoin(savingsVaultDenom, sdk.ZeroInt()), totalValue)
}
func (suite *strategySavingsTestSuite) TestGetVaultTotalValue_NoDenomDeposit() {
// 2 Vaults usdx, busd
// 1st vault has deposits
// 2nd vault has no deposits
vaultDenomBusd := "busd"
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
suite.CreateVault(vaultDenomBusd, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
// Deposit vault1
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Query vault total, savings deposit exists for account, but amount in busd does not
// Vault2 does not have any value, only returns amount for the correct denom
// if a savings deposit already exists
totalValueBusd, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenomBusd)
suite.Require().NoError(err)
suite.Equal(sdk.NewCoin(vaultDenomBusd, sdk.ZeroInt()), totalValueBusd)
}
// ----------------------------------------------------------------------------
// Withdraw
func (suite *strategySavingsTestSuite) TestWithdraw() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
suite.SavingsDepositAmountEqual(sdk.NewCoins(depositAmount))
// Query vault total
totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, savingsVaultDenom)
suite.Require().NoError(err)
suite.Equal(depositAmount, totalValue)
// Withdraw
err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
suite.SavingsDepositAmountEqual(sdk.NewCoins())
suite.VaultTotalValuesEqual(sdk.NewCoins())
suite.VaultTotalSharesEqual(types.NewVaultShares())
totalValue, err = suite.Keeper.GetVaultTotalValue(suite.Ctx, savingsVaultDenom)
suite.Require().NoError(err)
suite.Equal(sdk.NewInt64Coin(savingsVaultDenom, 0), totalValue)
// Withdraw again
err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().Error(err)
suite.Require().ErrorIs(err, types.ErrVaultRecordNotFound, "vault should be deleted when no more supply")
}
func (suite *strategySavingsTestSuite) TestWithdraw_OnlyWithdrawOwnSupply() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Deposits from 2 accounts
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Withdraw
err = suite.Keeper.Withdraw(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Withdraw again
err = suite.Keeper.Withdraw(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().Error(err)
suite.Require().ErrorIs(
err,
types.ErrVaultShareRecordNotFound,
"should only be able to withdraw the account's own supply",
)
}
func (suite *strategySavingsTestSuite) TestWithdraw_WithAccumulatedSavings() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Deposits accounts
acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
err := suite.Keeper.Deposit(suite.Ctx, acc, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Deposit from acc2 so the vault doesn't get deleted when withdrawing
err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Direct savings deposit from module account to increase vault value
err = suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 20)))
suite.Require().NoError(err)
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
err = suite.SavingsKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 20)))
suite.Require().NoError(err)
// Query account value
accValue, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc)
suite.Require().NoError(err)
suite.Equal(depositAmount.AddAmount(sdk.NewInt(10)), accValue)
// Withdraw 100, 10 remaining
err = suite.Keeper.Withdraw(suite.Ctx, acc, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Withdraw 100 again -- too much
err = suite.Keeper.Withdraw(suite.Ctx, acc, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().Error(err)
suite.Require().ErrorIs(
err,
types.ErrInsufficientValue,
"cannot withdraw more than account value",
)
// Half of remaining 10, 5 remaining
err = suite.Keeper.Withdraw(suite.Ctx, acc, sdk.NewCoin(savingsVaultDenom, sdk.NewInt(5)), types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// Withdraw all
err = suite.Keeper.Withdraw(suite.Ctx, acc, sdk.NewCoin(savingsVaultDenom, sdk.NewInt(5)), types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
accValue, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc)
suite.Require().Errorf(
err,
"account should be deleted when all shares withdrawn but has %s value still",
accValue,
)
suite.Require().Equal("account vault share record for ukava not found", err.Error())
}
func (suite *strategySavingsTestSuite) TestAccountShares() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 1000)))
suite.Require().NoError(err)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Deposit from account1
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
// 1. acc1 deposit 100
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().True(found)
suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(savingsVaultDenom), "initial deposit 1:1 shares")
// 2. Direct savings deposit from module account to increase vault value
// Total value: 100 -> 110
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
err = suite.SavingsKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 10)))
suite.Require().NoError(err)
// 2. acc2 deposit 100
// share price is 10% more expensive now
// savings 110 -> 210
err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// 100 * 100 / 210 = 47.619047619 shares
// 2.1 price * 47.619047619 = 99.9999999999
acc2Value, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc2)
suite.Require().NoError(err)
suite.Equal(
sdk.NewInt(99),
acc2Value.Amount,
"value 1 less than deposit amount with different share price, decimals truncated",
)
acc2Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc2)
suite.Require().True(found)
// 100 * 100 / 110 = 90.909090909090909091
// QuoInt64() truncates
expectedAcc2Shares := sdk.NewDec(100).MulInt64(100).QuoInt64(110)
suite.Equal(expectedAcc2Shares, acc2Shares.AmountOf(savingsVaultDenom))
vaultTotalShares, found := suite.Keeper.GetVaultTotalShares(suite.Ctx, savingsVaultDenom)
suite.Require().True(found)
suite.Equal(sdk.NewDec(100).Add(expectedAcc2Shares), vaultTotalShares.Amount)
// Savings deposit again from module account to triple original value
// 210 -> 300
err = suite.SavingsKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 90)))
suite.Require().NoError(err)
// Deposit again from acc1
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found = suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().True(found)
// totalShares = 100 + 90 = 190
// totalValue = 100 + 10 + 100 + 90 = 300
// sharesIssued = assetAmount * (shareCount / totalTokens)
// sharedIssued = 100 * 190 / 300 = 63.3 = 63
// total shares = 100 + 63 = 163
suite.Equal(
sdk.NewDec(100).Add(sdk.NewDec(100).Mul(vaultTotalShares.Amount).Quo(sdk.NewDec(300))),
acc1Shares.AmountOf(savingsVaultDenom),
"shares should consist of 100 of 1x share price and 63 of 3x share price",
)
}
func (suite *strategySavingsTestSuite) TestWithdraw_AccumulatedAmount() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 1000)))
suite.Require().NoError(err)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Deposit from account1
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
// 1. acc1 deposit 100
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// acc2 deposit 100, just to make sure other deposits do not affect acc1
err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().True(found)
suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(savingsVaultDenom), "initial deposit 1:1 shares")
// 2. Direct savings deposit from module account to increase vault value
// Total value: 200 -> 220, 110 each account
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
err = suite.SavingsKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 20)))
suite.Require().NoError(err)
// 3. Withdraw all from acc1 - including accumulated amount
err = suite.Keeper.Withdraw(suite.Ctx, acc1, depositAmount.AddAmount(sdk.NewInt(10)), types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
_, found = suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().False(found, "should have withdrawn entire shares")
}
func (suite *strategySavingsTestSuite) TestWithdraw_AccumulatedTruncated() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 1000)))
suite.Require().NoError(err)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Deposit from account1
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress()
// 1. acc1 deposit 100
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
// acc2 deposit 100, just to make sure other deposits do not affect acc1
err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().True(found)
suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(savingsVaultDenom), "initial deposit 1:1 shares")
// 2. Direct savings deposit from module account to increase vault value
// Total value: 200 -> 211, 105.5 each account
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
err = suite.SavingsKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 11)))
suite.Require().NoError(err)
accBal, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc1)
suite.Require().NoError(err)
suite.Equal(depositAmount.AddAmount(sdk.NewInt(5)), accBal, "acc1 should have 105 usdx")
// 3. Withdraw all from acc1 - including accumulated amount
err = suite.Keeper.Withdraw(suite.Ctx, acc1, depositAmount.AddAmount(sdk.NewInt(5)), types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found = suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().Falsef(found, "should have withdrawn entire shares but has %s", acc1Shares)
_, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc1)
suite.Require().Error(err)
}
func (suite *strategySavingsTestSuite) TestWithdraw_ExpensiveShares() {
startBalance := sdk.NewInt64Coin(savingsVaultDenom, 1000)
depositAmount := sdk.NewInt64Coin(savingsVaultDenom, 100)
err := suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 2000)))
suite.Require().NoError(err)
suite.CreateVault(savingsVaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
// Deposit from account1
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress()
// 1. acc1 deposit 100
err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().True(found)
suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(savingsVaultDenom), "initial deposit 1:1 shares")
// 2. Direct savings deposit from module account to increase vault value
// Total value: 100 -> 2000, shares now 10usdx each
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
err = suite.SavingsKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(savingsVaultDenom, 1900)))
suite.Require().NoError(err)
accBal, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc1)
suite.Require().NoError(err)
suite.Equal(sdk.NewInt(2000), accBal.Amount, "acc1 should have 2000 usdx")
// 3. Withdraw all from acc1 - including accumulated amount
err = suite.Keeper.Withdraw(suite.Ctx, acc1, sdk.NewInt64Coin(savingsVaultDenom, 2000), types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(err)
acc1Shares, found = suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1)
suite.Require().Falsef(found, "should have withdrawn entire shares but has %s", acc1Shares)
_, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, savingsVaultDenom, acc1)
suite.Require().Error(err)
}

View File

@ -7,9 +7,7 @@ import (
"github.com/kava-labs/kava/x/earn/types"
)
// GetVaultTotalShares returns the total balance supplied to the vault. This
// may not necessarily be the current value of the vault, as it is the sum
// of the supplied denom and the value may be higher due to accumulated APYs.
// GetVaultTotalShares returns the total shares of a vault.
func (k *Keeper) GetVaultTotalShares(
ctx sdk.Context,
denom string,
@ -22,9 +20,8 @@ func (k *Keeper) GetVaultTotalShares(
return vault.TotalShares, true
}
// GetTotalValue returns the total **value** of all coins in this vault,
// i.e. the realizable total value denominated by GetDenom() if the vault
// were to liquidate its entire strategies.
// GetVaultTotalValue returns the total value of a vault, i.e. the realizable
// total value if the vault were to liquidate its entire strategies.
//
// **Note:** This does not include the tokens held in bank by the module
// account. If it were to be included, also note that the module account is
@ -33,21 +30,21 @@ func (k *Keeper) GetVaultTotalValue(
ctx sdk.Context,
denom string,
) (sdk.Coin, error) {
enabledVault, found := k.GetAllowedVault(ctx, denom)
allowedVault, found := k.GetAllowedVault(ctx, denom)
if !found {
return sdk.Coin{}, types.ErrVaultRecordNotFound
}
strategy, err := k.GetStrategy(enabledVault.Strategies[0])
strategy, err := k.GetStrategy(allowedVault.Strategies[0])
if err != nil {
return sdk.Coin{}, types.ErrInvalidVaultStrategy
}
return strategy.GetEstimatedTotalAssets(ctx, enabledVault.Denom)
// Denom can be different from allowedVault.Denom for bkava
return strategy.GetEstimatedTotalAssets(ctx, denom)
}
// GetVaultAccountSupplied returns the supplied amount for a single address
// within a vault.
// GetVaultAccountShares returns the shares for a single address for all vaults.
func (k *Keeper) GetVaultAccountShares(
ctx sdk.Context,
acc sdk.AccAddress,

View File

@ -42,8 +42,7 @@ func (k *Keeper) UpdateVaultShareRecord(
}
}
// DeleteVaultShareRecord deletes the vault share record for a given denom and
// account.
// DeleteVaultShareRecord deletes the vault share record for a given account.
func (k *Keeper) DeleteVaultShareRecord(
ctx sdk.Context,
acc sdk.AccAddress,
@ -52,7 +51,7 @@ func (k *Keeper) DeleteVaultShareRecord(
store.Delete(types.DepositorVaultSharesKey(acc))
}
// SetVaultShareRecord sets the vault share record for a given denom and account.
// SetVaultShareRecord sets the vault share record for a given account.
func (k *Keeper) SetVaultShareRecord(
ctx sdk.Context,
record types.VaultShareRecord,

View File

@ -126,6 +126,9 @@ func (k *Keeper) Withdraw(
withdrawShares = vaultShareRecord.Shares.GetShare(withdrawAmount.Denom)
}
// Call hook before record is modified
k.BeforeVaultDepositModified(ctx, wantAmount.Denom, from, vaultRecord.TotalShares.Amount)
// Decrement VaultRecord and VaultShareRecord supplies - must delete same
// amounts
vaultShareRecord.Shares = vaultShareRecord.Shares.Sub(withdrawShares)

View File

@ -241,3 +241,33 @@ func (suite *withdrawTestSuite) TestWithdraw_Partial() {
sdk.NewCoins(),
)
}
func (suite *withdrawTestSuite) TestWithdraw_bKava() {
vaultDenom := "bkava"
coinDenom := vaultDenom + "-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l"
startBalance := sdk.NewInt64Coin(coinDenom, 1000)
depositAmount := sdk.NewInt64Coin(coinDenom, 100)
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
// 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, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(
err,
"should be able to deposit bkava derivative denom in bkava vault",
)
err = suite.Keeper.Withdraw(suite.Ctx, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
suite.Require().NoError(
err,
"should be able to withdraw bkava derivative denom from bkava vault",
)
}

View File

@ -14,6 +14,7 @@ import (
hardtypes "github.com/kava-labs/kava/x/hard/types"
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
savingskeeper "github.com/kava-labs/kava/x/savings/keeper"
savingstypes "github.com/kava-labs/kava/x/savings/types"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -141,12 +142,23 @@ func (suite *Suite) SetupTest() {
hardtypes.DefaultTotalReserves,
)
savingsGS := savingstypes.NewGenesisState(
savingstypes.NewParams(
[]string{
"ukava",
"bkava-kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l",
},
),
nil,
)
tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates(
app.GenesisState{
pricefeedtypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&pricefeedGS),
hardtypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&hardGS),
savingstypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&savingsGS),
},
)
@ -251,6 +263,8 @@ func (suite *Suite) ModuleAccountBalanceEqual(coins sdk.Coins) {
// ----------------------------------------------------------------------------
// Earn
// VaultTotalValuesEqual asserts that the vault total values match the provided
// values.
func (suite *Suite) VaultTotalValuesEqual(expected sdk.Coins) {
for _, coin := range expected {
vaultBal, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, coin.Denom)
@ -259,6 +273,8 @@ func (suite *Suite) VaultTotalValuesEqual(expected sdk.Coins) {
}
}
// VaultTotalSharesEqual asserts that the vault total shares match the provided
// values.
func (suite *Suite) VaultTotalSharesEqual(expected types.VaultShares) {
for _, share := range expected {
vaultBal, found := suite.Keeper.GetVaultTotalShares(suite.Ctx, share.Denom)
@ -267,6 +283,8 @@ func (suite *Suite) VaultTotalSharesEqual(expected types.VaultShares) {
}
}
// VaultAccountSharesEqual asserts that the vault account shares match the provided
// values.
func (suite *Suite) VaultAccountSharesEqual(accs []sdk.AccAddress, supplies []sdk.Coins) {
for i, acc := range accs {
coins := supplies[i]
@ -288,6 +306,8 @@ func (suite *Suite) VaultAccountSharesEqual(accs []sdk.AccAddress, supplies []sd
// ----------------------------------------------------------------------------
// Hard
// HardDepositAmountEqual asserts that the hard deposit amount matches the provided
// values.
func (suite *Suite) HardDepositAmountEqual(expected sdk.Coins) {
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
@ -306,6 +326,29 @@ func (suite *Suite) HardDepositAmountEqual(expected sdk.Coins) {
)
}
// ----------------------------------------------------------------------------
// Savings
// SavingsDepositAmountEqual asserts that the savings deposit amount matches the
// provided values.
func (suite *Suite) SavingsDepositAmountEqual(expected sdk.Coins) {
macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
savingsDeposit, found := suite.SavingsKeeper.GetDeposit(suite.Ctx, macc.GetAddress())
if expected.IsZero() {
suite.Require().False(found)
return
}
suite.Require().True(found, "savings should have a deposit")
suite.Require().Equalf(
expected,
savingsDeposit.Amount,
"savings should have a deposit with the amount %v",
expected,
)
}
// ----------------------------------------------------------------------------
// EventsContains asserts that the expected event is in the provided events

View File

@ -7,7 +7,7 @@ import (
// earn module errors
var (
ErrInvalidVaultDenom = sdkerrors.Register(ModuleName, 2, "invalid vault denom")
ErrInvalidVaultStrategy = sdkerrors.Register(ModuleName, 3, "invalid vault strategy")
ErrInvalidVaultStrategy = sdkerrors.Register(ModuleName, 3, "vault does not support this strategy")
ErrInsufficientAmount = sdkerrors.Register(ModuleName, 4, "insufficient amount")
ErrInsufficientValue = sdkerrors.Register(ModuleName, 5, "insufficient vault account value")
ErrVaultRecordNotFound = sdkerrors.Register(ModuleName, 6, "vault record not found")

View File

@ -40,3 +40,9 @@ type SavingsKeeper interface {
GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (savingstypes.Deposit, bool)
}
// EarnHooks are event hooks called when a user's deposit to a earn vault changes.
type EarnHooks interface {
AfterVaultDepositCreated(ctx sdk.Context, vaultDenom string, depositor sdk.AccAddress, sharedOwned sdk.Dec)
BeforeVaultDepositModified(ctx sdk.Context, vaultDenom string, depositor sdk.AccAddress, sharedOwned sdk.Dec)
}

View File

@ -28,7 +28,7 @@ var (
VaultShareRecordKeyPrefix = []byte{0x02} // depositor address -> vault shares
)
// Vault returns a key generated from a vault denom
// VaultKey returns a key generated from a vault denom
func VaultKey(denom string) []byte {
return []byte(denom)
}

View File

@ -0,0 +1,38 @@
// Code generated by mockery v2.14.0. DO NOT EDIT.
package mocks
import (
types "github.com/cosmos/cosmos-sdk/types"
mock "github.com/stretchr/testify/mock"
)
// EarnHooks is an autogenerated mock type for the EarnHooks type
type EarnHooks struct {
mock.Mock
}
// AfterVaultDepositCreated provides a mock function with given fields: ctx, vaultDenom, depositor, sharedOwned
func (_m *EarnHooks) AfterVaultDepositCreated(ctx types.Context, vaultDenom string, depositor types.AccAddress, sharedOwned types.Dec) {
_m.Called(ctx, vaultDenom, depositor, sharedOwned)
}
// BeforeVaultDepositModified provides a mock function with given fields: ctx, vaultDenom, depositor, sharedOwned
func (_m *EarnHooks) BeforeVaultDepositModified(ctx types.Context, vaultDenom string, depositor types.AccAddress, sharedOwned types.Dec) {
_m.Called(ctx, vaultDenom, depositor, sharedOwned)
}
type mockConstructorTestingTNewEarnHooks interface {
mock.TestingT
Cleanup(func())
}
// NewEarnHooks creates a new instance of EarnHooks. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewEarnHooks(t mockConstructorTestingTNewEarnHooks) *EarnHooks {
mock := &EarnHooks{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

View File

@ -30,8 +30,8 @@ func NewMsgDeposit(depositor string, amount sdk.Coin, strategy StrategyType) *Ms
// ValidateBasic does a simple validation check that doesn't require access to any other information.
func (msg MsgDeposit) ValidateBasic() error {
if msg.Depositor == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "depositor address cannot be empty")
if _, err := sdk.AccAddressFromBech32(msg.Depositor); err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
}
if err := msg.Amount.Validate(); err != nil {
@ -82,8 +82,8 @@ func NewMsgWithdraw(from string, amount sdk.Coin, strategy StrategyType) *MsgWit
// ValidateBasic does a simple validation check that doesn't require access to any other information.
func (msg MsgWithdraw) ValidateBasic() error {
if msg.From == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "depositor address cannot be empty")
if _, err := sdk.AccAddressFromBech32(msg.From); err != nil {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, err.Error())
}
if err := msg.Amount.Validate(); err != nil {

View File

@ -8,20 +8,25 @@ func NewQueryParamsRequest() *QueryParamsRequest {
}
// NewQueryVaultsRequest returns a new QueryVaultsRequest
func NewQueryVaultsRequest(denom string) *QueryVaultsRequest {
return &QueryVaultsRequest{
func NewQueryVaultsRequest() *QueryVaultsRequest {
return &QueryVaultsRequest{}
}
// NewQueryVaultRequest returns a new QueryVaultRequest
func NewQueryVaultRequest(denom string) *QueryVaultRequest {
return &QueryVaultRequest{
Denom: denom,
}
}
// NewQueryDepositsRequest returns a new QueryDepositsRequest
func NewQueryDepositsRequest(
owner string,
depositor string,
denom string,
pagination *query.PageRequest,
) *QueryDepositsRequest {
return &QueryDepositsRequest{
Owner: owner,
Depositor: depositor,
Denom: denom,
Pagination: pagination,
}

View File

@ -109,10 +109,8 @@ func (m *QueryParamsResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_QueryParamsResponse proto.InternalMessageInfo
// QueryVaultsRequest is the request type for the Query/Vault RPC method.
// QueryVaultsRequest is the request type for the Query/Vaults RPC method.
type QueryVaultsRequest struct {
// vault filters vault by denom
Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"`
}
func (m *QueryVaultsRequest) Reset() { *m = QueryVaultsRequest{} }
@ -187,6 +185,84 @@ func (m *QueryVaultsResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_QueryVaultsResponse proto.InternalMessageInfo
// QueryVaultRequest is the request type for the Query/Vault RPC method.
type QueryVaultRequest struct {
// vault filters vault by denom
Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"`
}
func (m *QueryVaultRequest) Reset() { *m = QueryVaultRequest{} }
func (m *QueryVaultRequest) String() string { return proto.CompactTextString(m) }
func (*QueryVaultRequest) ProtoMessage() {}
func (*QueryVaultRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_63f8dee2f3192a6b, []int{4}
}
func (m *QueryVaultRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *QueryVaultRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_QueryVaultRequest.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *QueryVaultRequest) XXX_Merge(src proto.Message) {
xxx_messageInfo_QueryVaultRequest.Merge(m, src)
}
func (m *QueryVaultRequest) XXX_Size() int {
return m.Size()
}
func (m *QueryVaultRequest) XXX_DiscardUnknown() {
xxx_messageInfo_QueryVaultRequest.DiscardUnknown(m)
}
var xxx_messageInfo_QueryVaultRequest proto.InternalMessageInfo
// QueryVaultResponse is the response type for the Query/Vault RPC method.
type QueryVaultResponse struct {
// vault represents the queried earn module vault
Vault VaultResponse `protobuf:"bytes,1,opt,name=vault,proto3" json:"vault"`
}
func (m *QueryVaultResponse) Reset() { *m = QueryVaultResponse{} }
func (m *QueryVaultResponse) String() string { return proto.CompactTextString(m) }
func (*QueryVaultResponse) ProtoMessage() {}
func (*QueryVaultResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_63f8dee2f3192a6b, []int{5}
}
func (m *QueryVaultResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *QueryVaultResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_QueryVaultResponse.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *QueryVaultResponse) XXX_Merge(src proto.Message) {
xxx_messageInfo_QueryVaultResponse.Merge(m, src)
}
func (m *QueryVaultResponse) XXX_Size() int {
return m.Size()
}
func (m *QueryVaultResponse) XXX_DiscardUnknown() {
xxx_messageInfo_QueryVaultResponse.DiscardUnknown(m)
}
var xxx_messageInfo_QueryVaultResponse proto.InternalMessageInfo
// VaultResponse is the response type for a vault.
type VaultResponse struct {
// denom represents the denom of the vault
@ -212,7 +288,7 @@ func (m *VaultResponse) Reset() { *m = VaultResponse{} }
func (m *VaultResponse) String() string { return proto.CompactTextString(m) }
func (*VaultResponse) ProtoMessage() {}
func (*VaultResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_63f8dee2f3192a6b, []int{4}
return fileDescriptor_63f8dee2f3192a6b, []int{6}
}
func (m *VaultResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -243,8 +319,8 @@ var xxx_messageInfo_VaultResponse proto.InternalMessageInfo
// QueryDepositsRequest is the request type for the Query/Deposits RPC method.
type QueryDepositsRequest struct {
// owner optionally filters deposits by owner
Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty"`
// depositor optionally filters deposits by depositor
Depositor string `protobuf:"bytes,1,opt,name=depositor,proto3" json:"depositor,omitempty"`
// denom optionally filters deposits by vault denom
Denom string `protobuf:"bytes,2,opt,name=denom,proto3" json:"denom,omitempty"`
// pagination defines an optional pagination for the request.
@ -255,7 +331,7 @@ func (m *QueryDepositsRequest) Reset() { *m = QueryDepositsRequest{} }
func (m *QueryDepositsRequest) String() string { return proto.CompactTextString(m) }
func (*QueryDepositsRequest) ProtoMessage() {}
func (*QueryDepositsRequest) Descriptor() ([]byte, []int) {
return fileDescriptor_63f8dee2f3192a6b, []int{5}
return fileDescriptor_63f8dee2f3192a6b, []int{7}
}
func (m *QueryDepositsRequest) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -296,7 +372,7 @@ func (m *QueryDepositsResponse) Reset() { *m = QueryDepositsResponse{} }
func (m *QueryDepositsResponse) String() string { return proto.CompactTextString(m) }
func (*QueryDepositsResponse) ProtoMessage() {}
func (*QueryDepositsResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_63f8dee2f3192a6b, []int{6}
return fileDescriptor_63f8dee2f3192a6b, []int{8}
}
func (m *QueryDepositsResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -341,7 +417,7 @@ func (m *DepositResponse) Reset() { *m = DepositResponse{} }
func (m *DepositResponse) String() string { return proto.CompactTextString(m) }
func (*DepositResponse) ProtoMessage() {}
func (*DepositResponse) Descriptor() ([]byte, []int) {
return fileDescriptor_63f8dee2f3192a6b, []int{7}
return fileDescriptor_63f8dee2f3192a6b, []int{9}
}
func (m *DepositResponse) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
@ -375,6 +451,8 @@ func init() {
proto.RegisterType((*QueryParamsResponse)(nil), "kava.earn.v1beta1.QueryParamsResponse")
proto.RegisterType((*QueryVaultsRequest)(nil), "kava.earn.v1beta1.QueryVaultsRequest")
proto.RegisterType((*QueryVaultsResponse)(nil), "kava.earn.v1beta1.QueryVaultsResponse")
proto.RegisterType((*QueryVaultRequest)(nil), "kava.earn.v1beta1.QueryVaultRequest")
proto.RegisterType((*QueryVaultResponse)(nil), "kava.earn.v1beta1.QueryVaultResponse")
proto.RegisterType((*VaultResponse)(nil), "kava.earn.v1beta1.VaultResponse")
proto.RegisterType((*QueryDepositsRequest)(nil), "kava.earn.v1beta1.QueryDepositsRequest")
proto.RegisterType((*QueryDepositsResponse)(nil), "kava.earn.v1beta1.QueryDepositsResponse")
@ -384,59 +462,61 @@ func init() {
func init() { proto.RegisterFile("kava/earn/v1beta1/query.proto", fileDescriptor_63f8dee2f3192a6b) }
var fileDescriptor_63f8dee2f3192a6b = []byte{
// 825 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xbf, 0x4f, 0x1b, 0x49,
0x14, 0xf6, 0xda, 0xd8, 0x82, 0xf1, 0xc1, 0x1d, 0x83, 0x4f, 0xb2, 0xcd, 0xb1, 0x36, 0x8b, 0x0e,
0xf6, 0x90, 0xbc, 0x7b, 0xf8, 0xa4, 0x4b, 0x13, 0x45, 0x8a, 0x83, 0x82, 0x48, 0x11, 0x91, 0x25,
0xa1, 0x88, 0x14, 0x59, 0x63, 0x3c, 0x5a, 0x56, 0xd8, 0x3b, 0x66, 0x67, 0x6c, 0x42, 0xa2, 0x34,
0xf4, 0x91, 0x22, 0xe5, 0x5f, 0x88, 0x52, 0x50, 0xf3, 0x47, 0x50, 0x22, 0xd2, 0x44, 0x29, 0x20,
0x81, 0xd4, 0xa9, 0x53, 0x46, 0xf3, 0x63, 0xfd, 0xdb, 0x71, 0x2a, 0xd8, 0x79, 0xef, 0x7d, 0xdf,
0xf7, 0xde, 0x7c, 0xf3, 0x0c, 0x16, 0xf6, 0x51, 0x0b, 0xd9, 0x18, 0x05, 0xbe, 0xdd, 0x5a, 0xab,
0x60, 0x86, 0xd6, 0xec, 0x83, 0x26, 0x0e, 0x8e, 0xac, 0x46, 0x40, 0x18, 0x81, 0xb3, 0x3c, 0x6c,
0xf1, 0xb0, 0xa5, 0xc2, 0xd9, 0xcc, 0x2e, 0xa1, 0x75, 0x42, 0xcb, 0x22, 0xc1, 0x96, 0x1f, 0x32,
0x3b, 0xbb, 0x2a, 0xbf, 0xec, 0x0a, 0xa2, 0x58, 0xc2, 0xb4, 0x41, 0x1b, 0xc8, 0xf5, 0x7c, 0xc4,
0x3c, 0xe2, 0xab, 0x5c, 0xbd, 0x3b, 0x37, 0xcc, 0xda, 0x25, 0x5e, 0x18, 0x4f, 0xb9, 0xc4, 0x25,
0x92, 0x83, 0xff, 0xa7, 0x4e, 0xff, 0x72, 0x09, 0x71, 0x6b, 0xd8, 0x46, 0x0d, 0xcf, 0x46, 0xbe,
0x4f, 0x98, 0x80, 0x0c, 0xf9, 0xf5, 0xc1, 0x66, 0x1a, 0x28, 0x40, 0xf5, 0x30, 0x9e, 0x1f, 0x8c,
0x53, 0x16, 0x20, 0x86, 0x5d, 0xd5, 0x6f, 0x76, 0xc8, 0x38, 0x5a, 0xa8, 0x59, 0x63, 0x32, 0x6c,
0xa4, 0x00, 0x7c, 0xc4, 0xdb, 0xda, 0x12, 0xa8, 0x0e, 0x3e, 0x68, 0x62, 0xca, 0x8c, 0x87, 0x60,
0xae, 0xe7, 0x94, 0x36, 0x88, 0x4f, 0x31, 0xbc, 0x05, 0x12, 0x92, 0x3d, 0xad, 0xe5, 0x35, 0x33,
0x59, 0xcc, 0x58, 0x03, 0xc3, 0xb4, 0x64, 0x49, 0x69, 0xe2, 0xec, 0x32, 0x17, 0x71, 0x54, 0xba,
0xb1, 0xaa, 0x58, 0x76, 0x38, 0x73, 0xc8, 0x02, 0x53, 0x20, 0x5e, 0xc5, 0x3e, 0xa9, 0x0b, 0xb4,
0x29, 0x47, 0x7e, 0x18, 0x4f, 0x14, 0x77, 0x98, 0xab, 0xb8, 0xef, 0x80, 0x84, 0xd0, 0xcd, 0xb9,
0x63, 0x66, 0xb2, 0x98, 0x1f, 0xc2, 0x2d, 0x4a, 0xc2, 0x8a, 0x50, 0x82, 0xac, 0x32, 0xbe, 0x45,
0xc1, 0x74, 0x4f, 0x7c, 0x38, 0x3d, 0x74, 0x00, 0x50, 0x13, 0xf4, 0x30, 0x4d, 0x47, 0xf3, 0x31,
0x73, 0xa6, 0x98, 0x1b, 0xc2, 0xb5, 0xad, 0xc6, 0xfc, 0xf8, 0xa8, 0x81, 0x4b, 0xb3, 0x27, 0x57,
0xb9, 0xe9, 0xee, 0x13, 0xea, 0x74, 0xa1, 0x40, 0x13, 0xfc, 0xe1, 0x71, 0x7b, 0x79, 0x2d, 0xc4,
0x70, 0x59, 0x08, 0x4a, 0xc7, 0xf2, 0x9a, 0x39, 0xe9, 0xcc, 0x78, 0x74, 0x4b, 0x1e, 0x0b, 0x6d,
0x70, 0x03, 0x40, 0x54, 0xab, 0x91, 0x43, 0x5c, 0x2d, 0x57, 0x71, 0x83, 0x50, 0x8f, 0x91, 0x80,
0xa6, 0x27, 0xf2, 0x31, 0x73, 0xaa, 0x94, 0xbe, 0x38, 0x2d, 0xa4, 0x94, 0x3b, 0xef, 0x56, 0xab,
0x01, 0xa6, 0x74, 0x9b, 0x05, 0x9e, 0xef, 0x3a, 0xb3, 0xaa, 0x66, 0xbd, 0x5d, 0x02, 0x17, 0xc1,
0x6f, 0x8c, 0x30, 0x54, 0x2b, 0xd3, 0x3d, 0x14, 0x60, 0x9a, 0x8e, 0x8b, 0x1e, 0x93, 0xe2, 0x6c,
0x5b, 0x1c, 0xc1, 0x67, 0x40, 0x7e, 0x96, 0x5b, 0xa8, 0xd6, 0xc4, 0xe9, 0x04, 0xcf, 0x28, 0xdd,
0xe6, 0x43, 0xfb, 0x74, 0x99, 0x5b, 0x76, 0x3d, 0xb6, 0xd7, 0xac, 0x58, 0xbb, 0xa4, 0xae, 0x5e,
0x84, 0xfa, 0x53, 0xa0, 0xd5, 0x7d, 0x9b, 0xf1, 0x16, 0xad, 0x4d, 0x9f, 0x5d, 0x9c, 0x16, 0x80,
0x92, 0xb4, 0xe9, 0x33, 0x07, 0x08, 0xc0, 0x1d, 0x8e, 0x67, 0xbc, 0xd3, 0x40, 0x4a, 0x5c, 0xa4,
0x52, 0xd5, 0xbe, 0x76, 0x0b, 0xc4, 0xc9, 0xa1, 0x8f, 0x03, 0x39, 0xf7, 0x9f, 0xb4, 0x25, 0xd3,
0x3a, 0xf7, 0x14, 0xed, 0xbe, 0xa7, 0xfb, 0x00, 0x74, 0x5e, 0xa0, 0x98, 0x66, 0xb2, 0xb8, 0x6c,
0x29, 0x1c, 0xfe, 0x04, 0x2d, 0xf9, 0xea, 0x3b, 0xbe, 0x74, 0xb1, 0x52, 0xe0, 0x74, 0x55, 0x1a,
0xef, 0x35, 0xf0, 0x67, 0x9f, 0x4c, 0xe5, 0x8f, 0x75, 0x30, 0xa9, 0xee, 0x20, 0xf4, 0x9c, 0x31,
0xc4, 0x07, 0xaa, 0xac, 0xcf, 0x75, 0xed, 0x4a, 0xb8, 0xd1, 0xa3, 0x33, 0x2a, 0x74, 0xae, 0x8c,
0xd5, 0x29, 0xc1, 0x7a, 0x84, 0x7e, 0xd7, 0xc0, 0xef, 0x7d, 0x64, 0xf0, 0x7f, 0x30, 0xd5, 0xb6,
0xc9, 0xd8, 0x71, 0x76, 0x52, 0xe1, 0x03, 0x90, 0x50, 0xbe, 0x88, 0x8a, 0xc6, 0x16, 0x46, 0x3d,
0x26, 0x61, 0x95, 0xd2, 0x1c, 0xef, 0xe9, 0xe4, 0x2a, 0x97, 0xec, 0x9c, 0x51, 0x47, 0x21, 0x40,
0x04, 0xe2, 0xd2, 0x40, 0x31, 0x01, 0x95, 0xe9, 0xe9, 0x2d, 0x04, 0xbb, 0x47, 0x3c, 0xbf, 0xf4,
0xaf, 0x82, 0x31, 0x7f, 0xc1, 0x5b, 0xbc, 0x80, 0x3a, 0x12, 0xb9, 0xf8, 0x3a, 0x06, 0xe2, 0xe2,
0x8e, 0xe0, 0x0b, 0x90, 0x90, 0x0b, 0x06, 0xfe, 0x3d, 0x44, 0xf2, 0xe0, 0x26, 0xcb, 0x2e, 0x8f,
0x4b, 0x93, 0x93, 0x34, 0x16, 0x8f, 0x3f, 0x7c, 0x7d, 0x1b, 0x9d, 0x87, 0x19, 0x7b, 0xd4, 0xc6,
0x85, 0xc7, 0x1a, 0x48, 0xc8, 0xa5, 0x34, 0x9a, 0xbc, 0x67, 0xc1, 0x8d, 0x26, 0xef, 0xdd, 0x6d,
0xc6, 0x3f, 0x82, 0x7c, 0x09, 0x2e, 0xda, 0x23, 0x96, 0x35, 0xb5, 0x5f, 0x0a, 0xd7, 0xbf, 0xe2,
0x22, 0x26, 0x43, 0xa7, 0xc2, 0x95, 0x51, 0xf8, 0x7d, 0x4f, 0x2e, 0x6b, 0x8e, 0x4f, 0x54, 0x52,
0x96, 0x84, 0x94, 0x05, 0x38, 0x3f, 0x44, 0x4a, 0xe8, 0xe9, 0xd2, 0xfa, 0xd9, 0x17, 0x3d, 0x72,
0x76, 0xad, 0x6b, 0xe7, 0xd7, 0xba, 0xf6, 0xf9, 0x5a, 0xd7, 0xde, 0xdc, 0xe8, 0x91, 0xf3, 0x1b,
0x3d, 0xf2, 0xf1, 0x46, 0x8f, 0x3c, 0xed, 0x5e, 0x1d, 0x1c, 0xa4, 0x50, 0x43, 0x15, 0x2a, 0xe1,
0x9e, 0x4b, 0x40, 0x71, 0xc5, 0x95, 0x84, 0xf8, 0x05, 0xfa, 0xef, 0x47, 0x00, 0x00, 0x00, 0xff,
0xff, 0xa7, 0x27, 0xb6, 0xa1, 0xb1, 0x07, 0x00, 0x00,
// 855 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x4f, 0x1b, 0x47,
0x14, 0xf7, 0xda, 0xd8, 0x82, 0x71, 0xa1, 0xf5, 0xe0, 0x4a, 0xb6, 0x29, 0x6b, 0xb3, 0x14, 0x58,
0x2a, 0x79, 0xb7, 0xb8, 0x52, 0x7b, 0x41, 0x95, 0xea, 0xa2, 0x22, 0x7a, 0xa8, 0xe8, 0xd2, 0x72,
0xa8, 0x54, 0x59, 0x63, 0x3c, 0x5a, 0x56, 0xd8, 0x3b, 0x66, 0x67, 0xec, 0x96, 0x56, 0x51, 0x24,
0xa4, 0xdc, 0x23, 0xe5, 0x7f, 0xc8, 0x01, 0xe5, 0xc8, 0x1f, 0xc1, 0x11, 0x91, 0x4b, 0x94, 0x03,
0x24, 0x90, 0x73, 0xce, 0x39, 0x46, 0xf3, 0xb1, 0xfe, 0x36, 0x46, 0x39, 0xc1, 0xbe, 0x8f, 0xdf,
0xef, 0xf7, 0xde, 0xbc, 0xf7, 0x0c, 0x16, 0x8f, 0x50, 0x1b, 0xd9, 0x18, 0x05, 0xbe, 0xdd, 0xde,
0xa8, 0x62, 0x86, 0x36, 0xec, 0xe3, 0x16, 0x0e, 0x4e, 0xac, 0x66, 0x40, 0x18, 0x81, 0x29, 0xee,
0xb6, 0xb8, 0xdb, 0x52, 0xee, 0x5c, 0xf6, 0x80, 0xd0, 0x06, 0xa1, 0x15, 0x11, 0x60, 0xcb, 0x0f,
0x19, 0x9d, 0xfb, 0x46, 0x7e, 0xd9, 0x55, 0x44, 0xb1, 0x84, 0xe9, 0x80, 0x36, 0x91, 0xeb, 0xf9,
0x88, 0x79, 0xc4, 0x57, 0xb1, 0x7a, 0x6f, 0x6c, 0x18, 0x75, 0x40, 0xbc, 0xd0, 0x9f, 0x76, 0x89,
0x4b, 0x24, 0x07, 0xff, 0x4f, 0x59, 0xbf, 0x72, 0x09, 0x71, 0xeb, 0xd8, 0x46, 0x4d, 0xcf, 0x46,
0xbe, 0x4f, 0x98, 0x80, 0x0c, 0xf9, 0xf5, 0xe1, 0x62, 0x9a, 0x28, 0x40, 0x8d, 0xd0, 0x5f, 0x18,
0xf6, 0x53, 0x16, 0x20, 0x86, 0x5d, 0x55, 0x6f, 0x6e, 0x44, 0x3b, 0xda, 0xa8, 0x55, 0x67, 0xd2,
0x6d, 0xa4, 0x01, 0xfc, 0x9d, 0x97, 0xb5, 0x2b, 0x50, 0x1d, 0x7c, 0xdc, 0xc2, 0x94, 0x19, 0xbf,
0x81, 0xf9, 0x3e, 0x2b, 0x6d, 0x12, 0x9f, 0x62, 0xf8, 0x03, 0x48, 0x48, 0xf6, 0x8c, 0x56, 0xd0,
0xcc, 0x64, 0x29, 0x6b, 0x0d, 0x35, 0xd3, 0x92, 0x29, 0xe5, 0xa9, 0x8b, 0xeb, 0x7c, 0xc4, 0x51,
0xe1, 0x1d, 0x96, 0x7d, 0xce, 0xdc, 0x61, 0xf9, 0x53, 0xb1, 0x84, 0x56, 0xc5, 0xf2, 0x23, 0x48,
0x08, 0x85, 0x9c, 0x25, 0x66, 0x26, 0x4b, 0x85, 0x11, 0x2c, 0x22, 0x25, 0xcc, 0x08, 0xc9, 0x64,
0x96, 0xb1, 0x0e, 0x52, 0x5d, 0x58, 0xc5, 0x05, 0xd3, 0x20, 0x5e, 0xc3, 0x3e, 0x69, 0x08, 0xe5,
0x33, 0x8e, 0xfc, 0x30, 0x9c, 0x5e, 0x5d, 0x1d, 0x01, 0x9b, 0x20, 0x2e, 0xa0, 0x54, 0x95, 0x0f,
0xe5, 0x97, 0x49, 0xc6, 0xfb, 0x28, 0x98, 0xed, 0xc7, 0x1b, 0xc9, 0x0d, 0x1d, 0x00, 0xd4, 0x53,
0x79, 0x98, 0x66, 0xa2, 0x85, 0x98, 0x39, 0x57, 0xca, 0x8f, 0xa0, 0xda, 0x53, 0xef, 0xf9, 0xc7,
0x49, 0x13, 0x97, 0x53, 0x67, 0x37, 0xf9, 0xd9, 0x5e, 0x0b, 0x75, 0x7a, 0x50, 0xa0, 0x09, 0xbe,
0xf0, 0xf8, 0x1c, 0x7b, 0x6d, 0xc4, 0x70, 0x45, 0x16, 0x11, 0x2b, 0x68, 0xe6, 0xb4, 0x33, 0xe7,
0xd1, 0x5d, 0x69, 0x16, 0xda, 0xe0, 0x36, 0x80, 0xa8, 0x5e, 0x27, 0xff, 0xe0, 0x5a, 0xa5, 0x86,
0x9b, 0x84, 0x7a, 0x8c, 0x04, 0x34, 0x33, 0x55, 0x88, 0x99, 0x33, 0xe5, 0xcc, 0xd5, 0x79, 0x31,
0xad, 0xd6, 0xe0, 0xa7, 0x5a, 0x2d, 0xc0, 0x94, 0xee, 0xb1, 0xc0, 0xf3, 0x5d, 0x27, 0xa5, 0x72,
0xb6, 0x3a, 0x29, 0x70, 0x09, 0x7c, 0xc6, 0x08, 0x43, 0xf5, 0x0a, 0x3d, 0x44, 0x01, 0xa6, 0x99,
0xb8, 0xa8, 0x31, 0x29, 0x6c, 0x7b, 0xc2, 0x04, 0xff, 0x06, 0xf2, 0xb3, 0xd2, 0x46, 0xf5, 0x16,
0xce, 0x24, 0x78, 0x44, 0x79, 0x93, 0xf7, 0xec, 0xf5, 0x75, 0x7e, 0xd5, 0xf5, 0xd8, 0x61, 0xab,
0x6a, 0x1d, 0x90, 0x86, 0x5a, 0x3d, 0xf5, 0xa7, 0x48, 0x6b, 0x47, 0x36, 0xe3, 0x25, 0x5a, 0x3b,
0x3e, 0xbb, 0x3a, 0x2f, 0x02, 0x25, 0x69, 0xc7, 0x67, 0x0e, 0x10, 0x80, 0xfb, 0x1c, 0xcf, 0x78,
0xa1, 0x81, 0xb4, 0x78, 0x45, 0xa5, 0x2a, 0x9c, 0x2f, 0xf8, 0x3d, 0x98, 0xe9, 0xd4, 0x26, 0x7b,
0x7f, 0x4f, 0x69, 0xdd, 0xd0, 0xee, 0x7b, 0x45, 0x7b, 0xdf, 0xeb, 0x17, 0x00, 0xba, 0x2b, 0x2f,
0xba, 0x9a, 0x2c, 0xad, 0x5a, 0x0a, 0x8b, 0xef, 0xbc, 0x25, 0xcf, 0x4c, 0x77, 0x11, 0x5c, 0xac,
0x94, 0x38, 0x3d, 0x99, 0xc6, 0x73, 0x0d, 0x7c, 0x39, 0x20, 0x57, 0xcd, 0xc9, 0x16, 0x98, 0x56,
0x22, 0xc2, 0xd1, 0x37, 0x46, 0xcc, 0x83, 0x4a, 0x1b, 0x18, 0xbe, 0x4e, 0x26, 0xdc, 0xee, 0xd3,
0x19, 0x15, 0x3a, 0xd7, 0x26, 0xea, 0x94, 0x60, 0x7d, 0x42, 0x3f, 0x68, 0xe0, 0xf3, 0x01, 0xb2,
0x4f, 0x6e, 0xe9, 0xaf, 0x20, 0xa1, 0xe6, 0x23, 0x2a, 0x0a, 0x5b, 0x1c, 0xb7, 0x53, 0x62, 0x64,
0xca, 0xf3, 0xbc, 0xa6, 0xb3, 0x9b, 0x7c, 0xb2, 0x6b, 0xa3, 0x8e, 0x42, 0x80, 0x88, 0xaf, 0x27,
0x1f, 0xa4, 0x98, 0x80, 0xca, 0xf6, 0xd5, 0x16, 0x82, 0xfd, 0x4c, 0x3c, 0xbf, 0xfc, 0xad, 0x82,
0x31, 0x1f, 0x30, 0x63, 0x3c, 0x81, 0x3a, 0x12, 0xb9, 0xf4, 0x64, 0x0a, 0xc4, 0xc5, 0x1b, 0xc1,
0xff, 0x40, 0x42, 0x5e, 0x34, 0xb8, 0x32, 0x42, 0xf2, 0xf0, 0xe9, 0xcc, 0xad, 0x4e, 0x0a, 0x93,
0x9d, 0x34, 0x96, 0x4e, 0x5f, 0xbe, 0x7b, 0x16, 0x5d, 0x80, 0x59, 0x7b, 0xdc, 0x89, 0xe7, 0xdc,
0xf2, 0x34, 0x8e, 0xe7, 0xee, 0x3b, 0xa8, 0xe3, 0xb9, 0xfb, 0x2f, 0xec, 0xbd, 0xdc, 0xf2, 0x88,
0xc2, 0xc7, 0x20, 0x2e, 0x0f, 0xc5, 0xd7, 0xf7, 0x62, 0x86, 0xcc, 0x2b, 0x13, 0xa2, 0x14, 0xf1,
0xba, 0x20, 0x5e, 0x86, 0x4b, 0x63, 0x89, 0xed, 0xff, 0xc5, 0xb6, 0x3d, 0x82, 0xa7, 0x1a, 0x98,
0x0e, 0x37, 0x04, 0xae, 0x8d, 0x83, 0x1f, 0x58, 0xf9, 0x9c, 0x39, 0x39, 0x50, 0x49, 0x59, 0x16,
0x52, 0x16, 0xe1, 0xc2, 0x08, 0x29, 0xe1, 0x2e, 0x95, 0xb7, 0x2e, 0xde, 0xea, 0x91, 0x8b, 0x5b,
0x5d, 0xbb, 0xbc, 0xd5, 0xb5, 0x37, 0xb7, 0xba, 0xf6, 0xf4, 0x4e, 0x8f, 0x5c, 0xde, 0xe9, 0x91,
0x57, 0x77, 0x7a, 0xe4, 0xaf, 0xde, 0xd3, 0xc5, 0x41, 0x8a, 0x75, 0x54, 0xa5, 0x12, 0xee, 0x5f,
0x09, 0x28, 0x46, 0xab, 0x9a, 0x10, 0x3f, 0xb5, 0xdf, 0x7d, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xf4,
0x79, 0x11, 0x40, 0x9a, 0x08, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -453,9 +533,11 @@ const _ = grpc.SupportPackageIsVersion4
type QueryClient interface {
// Params queries all parameters of the earn module.
Params(ctx context.Context, in *QueryParamsRequest, opts ...grpc.CallOption) (*QueryParamsResponse, error)
// Vaults queries vaults based on vault denom
// Vaults queries all vaults
Vaults(ctx context.Context, in *QueryVaultsRequest, opts ...grpc.CallOption) (*QueryVaultsResponse, error)
// Deposits queries deposit details based on owner address and vault
// Vault queries a single vault based on the vault denom
Vault(ctx context.Context, in *QueryVaultRequest, opts ...grpc.CallOption) (*QueryVaultResponse, error)
// Deposits queries deposit details based on depositor address and vault
Deposits(ctx context.Context, in *QueryDepositsRequest, opts ...grpc.CallOption) (*QueryDepositsResponse, error)
}
@ -485,6 +567,15 @@ func (c *queryClient) Vaults(ctx context.Context, in *QueryVaultsRequest, opts .
return out, nil
}
func (c *queryClient) Vault(ctx context.Context, in *QueryVaultRequest, opts ...grpc.CallOption) (*QueryVaultResponse, error) {
out := new(QueryVaultResponse)
err := c.cc.Invoke(ctx, "/kava.earn.v1beta1.Query/Vault", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *queryClient) Deposits(ctx context.Context, in *QueryDepositsRequest, opts ...grpc.CallOption) (*QueryDepositsResponse, error) {
out := new(QueryDepositsResponse)
err := c.cc.Invoke(ctx, "/kava.earn.v1beta1.Query/Deposits", in, out, opts...)
@ -498,9 +589,11 @@ func (c *queryClient) Deposits(ctx context.Context, in *QueryDepositsRequest, op
type QueryServer interface {
// Params queries all parameters of the earn module.
Params(context.Context, *QueryParamsRequest) (*QueryParamsResponse, error)
// Vaults queries vaults based on vault denom
// Vaults queries all vaults
Vaults(context.Context, *QueryVaultsRequest) (*QueryVaultsResponse, error)
// Deposits queries deposit details based on owner address and vault
// Vault queries a single vault based on the vault denom
Vault(context.Context, *QueryVaultRequest) (*QueryVaultResponse, error)
// Deposits queries deposit details based on depositor address and vault
Deposits(context.Context, *QueryDepositsRequest) (*QueryDepositsResponse, error)
}
@ -514,6 +607,9 @@ func (*UnimplementedQueryServer) Params(ctx context.Context, req *QueryParamsReq
func (*UnimplementedQueryServer) Vaults(ctx context.Context, req *QueryVaultsRequest) (*QueryVaultsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Vaults not implemented")
}
func (*UnimplementedQueryServer) Vault(ctx context.Context, req *QueryVaultRequest) (*QueryVaultResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Vault not implemented")
}
func (*UnimplementedQueryServer) Deposits(ctx context.Context, req *QueryDepositsRequest) (*QueryDepositsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Deposits not implemented")
}
@ -558,6 +654,24 @@ func _Query_Vaults_Handler(srv interface{}, ctx context.Context, dec func(interf
return interceptor(ctx, in, info, handler)
}
func _Query_Vault_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryVaultRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(QueryServer).Vault(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/kava.earn.v1beta1.Query/Vault",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(QueryServer).Vault(ctx, req.(*QueryVaultRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Query_Deposits_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(QueryDepositsRequest)
if err := dec(in); err != nil {
@ -588,6 +702,10 @@ var _Query_serviceDesc = grpc.ServiceDesc{
MethodName: "Vaults",
Handler: _Query_Vaults_Handler,
},
{
MethodName: "Vault",
Handler: _Query_Vault_Handler,
},
{
MethodName: "Deposits",
Handler: _Query_Deposits_Handler,
@ -673,13 +791,6 @@ func (m *QueryVaultsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.Denom) > 0 {
i -= len(m.Denom)
copy(dAtA[i:], m.Denom)
i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
@ -720,6 +831,69 @@ func (m *QueryVaultsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
return len(dAtA) - i, nil
}
func (m *QueryVaultRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *QueryVaultRequest) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *QueryVaultRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Denom) > 0 {
i -= len(m.Denom)
copy(dAtA[i:], m.Denom)
i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *QueryVaultResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *QueryVaultResponse) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *QueryVaultResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
{
size, err := m.Vault.MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
i -= size
i = encodeVarintQuery(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func (m *VaultResponse) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
@ -777,20 +951,20 @@ func (m *VaultResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) {
dAtA[i] = 0x18
}
if len(m.Strategies) > 0 {
dAtA3 := make([]byte, len(m.Strategies)*10)
var j2 int
dAtA4 := make([]byte, len(m.Strategies)*10)
var j3 int
for _, num := range m.Strategies {
for num >= 1<<7 {
dAtA3[j2] = uint8(uint64(num)&0x7f | 0x80)
dAtA4[j3] = uint8(uint64(num)&0x7f | 0x80)
num >>= 7
j2++
j3++
}
dAtA3[j2] = uint8(num)
j2++
dAtA4[j3] = uint8(num)
j3++
}
i -= j2
copy(dAtA[i:], dAtA3[:j2])
i = encodeVarintQuery(dAtA, i, uint64(j2))
i -= j3
copy(dAtA[i:], dAtA4[:j3])
i = encodeVarintQuery(dAtA, i, uint64(j3))
i--
dAtA[i] = 0x12
}
@ -843,10 +1017,10 @@ func (m *QueryDepositsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x12
}
if len(m.Owner) > 0 {
i -= len(m.Owner)
copy(dAtA[i:], m.Owner)
i = encodeVarintQuery(dAtA, i, uint64(len(m.Owner)))
if len(m.Depositor) > 0 {
i -= len(m.Depositor)
copy(dAtA[i:], m.Depositor)
i = encodeVarintQuery(dAtA, i, uint64(len(m.Depositor)))
i--
dAtA[i] = 0xa
}
@ -997,10 +1171,6 @@ func (m *QueryVaultsRequest) Size() (n int) {
}
var l int
_ = l
l = len(m.Denom)
if l > 0 {
n += 1 + l + sovQuery(uint64(l))
}
return n
}
@ -1019,6 +1189,30 @@ func (m *QueryVaultsResponse) Size() (n int) {
return n
}
func (m *QueryVaultRequest) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Denom)
if l > 0 {
n += 1 + l + sovQuery(uint64(l))
}
return n
}
func (m *QueryVaultResponse) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = m.Vault.Size()
n += 1 + l + sovQuery(uint64(l))
return n
}
func (m *VaultResponse) Size() (n int) {
if m == nil {
return 0
@ -1060,7 +1254,7 @@ func (m *QueryDepositsRequest) Size() (n int) {
}
var l int
_ = l
l = len(m.Owner)
l = len(m.Depositor)
if l > 0 {
n += 1 + l + sovQuery(uint64(l))
}
@ -1287,38 +1481,6 @@ func (m *QueryVaultsRequest) Unmarshal(dAtA []byte) error {
return fmt.Errorf("proto: QueryVaultsRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthQuery
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthQuery
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Denom = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipQuery(dAtA[iNdEx:])
@ -1424,6 +1586,171 @@ func (m *QueryVaultsResponse) Unmarshal(dAtA []byte) error {
}
return nil
}
func (m *QueryVaultRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: QueryVaultRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: QueryVaultRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthQuery
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthQuery
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Denom = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipQuery(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthQuery
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *QueryVaultResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: QueryVaultResponse: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: QueryVaultResponse: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Vault", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowQuery
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthQuery
}
postIndex := iNdEx + msglen
if postIndex < 0 {
return ErrInvalidLengthQuery
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.Vault.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipQuery(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthQuery
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *VaultResponse) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
@ -1724,7 +2051,7 @@ func (m *QueryDepositsRequest) Unmarshal(dAtA []byte) error {
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Owner", wireType)
return fmt.Errorf("proto: wrong wireType = %d for field Depositor", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
@ -1752,7 +2079,7 @@ func (m *QueryDepositsRequest) Unmarshal(dAtA []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Owner = string(dAtA[iNdEx:postIndex])
m.Depositor = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {

View File

@ -53,24 +53,6 @@ func request_Query_Vaults_0(ctx context.Context, marshaler runtime.Marshaler, cl
var protoReq QueryVaultsRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["denom"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom")
}
protoReq.Denom, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
}
msg, err := client.Vaults(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
@ -80,6 +62,15 @@ func local_request_Query_Vaults_0(ctx context.Context, marshaler runtime.Marshal
var protoReq QueryVaultsRequest
var metadata runtime.ServerMetadata
msg, err := server.Vaults(ctx, &protoReq)
return msg, metadata, err
}
func request_Query_Vault_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryVaultRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
@ -98,7 +89,34 @@ func local_request_Query_Vaults_0(ctx context.Context, marshaler runtime.Marshal
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
}
msg, err := server.Vaults(ctx, &protoReq)
msg, err := client.Vault(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Query_Vault_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryVaultRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["denom"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom")
}
protoReq.Denom, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err)
}
msg, err := server.Vault(ctx, &protoReq)
return msg, metadata, err
}
@ -185,6 +203,26 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv
})
mux.Handle("GET", pattern_Query_Vault_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_Query_Vault_0(rctx, inboundMarshaler, server, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_Vault_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Query_Deposits_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -286,6 +324,26 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
})
mux.Handle("GET", pattern_Query_Vault_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_Query_Vault_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_Query_Vault_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Query_Deposits_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -312,7 +370,9 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie
var (
pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "earn", "v1beta1", "params"}, "", runtime.AssumeColonVerbOpt(false)))
pattern_Query_Vaults_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"kava", "earn", "v1beta1", "vaults", "denom"}, "", runtime.AssumeColonVerbOpt(false)))
pattern_Query_Vaults_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "earn", "v1beta1", "vaults"}, "", runtime.AssumeColonVerbOpt(false)))
pattern_Query_Vault_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"kava", "earn", "v1beta1", "vaults", "denom"}, "", runtime.AssumeColonVerbOpt(false)))
pattern_Query_Deposits_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "earn", "v1beta1", "deposits"}, "", runtime.AssumeColonVerbOpt(false)))
)
@ -322,5 +382,7 @@ var (
forward_Query_Vaults_0 = runtime.ForwardResponseMessage
forward_Query_Vault_0 = runtime.ForwardResponseMessage
forward_Query_Deposits_0 = runtime.ForwardResponseMessage
)

View File

@ -162,7 +162,7 @@ func (shares VaultShares) IsValid() bool {
return shares.Validate() == nil
}
// AmountOf returns the amount of shares of the given denom.
// Add adds two sets of VaultShares.
func (shares VaultShares) Add(sharesB ...VaultShare) VaultShares {
return shares.safeAdd(sharesB)
}
@ -284,8 +284,8 @@ func (shares VaultShares) negative() VaultShares {
}
// AmountOf returns the amount of shares of the given denom.
func (v VaultShares) AmountOf(denom string) sdk.Dec {
for _, s := range v {
func (shares VaultShares) AmountOf(denom string) sdk.Dec {
for _, s := range shares {
if s.Denom == denom {
return s.Amount
}
@ -295,8 +295,8 @@ func (v VaultShares) AmountOf(denom string) sdk.Dec {
}
// GetShare the single share of the given denom.
func (v VaultShares) GetShare(denom string) VaultShare {
for _, s := range v {
func (shares VaultShares) GetShare(denom string) VaultShare {
for _, s := range shares {
if s.Denom == denom {
return s
}
@ -306,8 +306,8 @@ func (v VaultShares) GetShare(denom string) VaultShare {
}
// IsZero returns true if the VaultShares is empty.
func (v VaultShares) IsZero() bool {
for _, s := range v {
func (shares VaultShares) IsZero() bool {
for _, s := range shares {
// If any amount is non-zero, false
if !s.Amount.IsZero() {
return false
@ -366,7 +366,7 @@ func removeZeroShares(shares VaultShares) VaultShares {
// ----------------------------------------------------------------------------
// VaultShares sort interface
func (a VaultShares) Len() int { return len(a) }
func (shares VaultShares) Len() int { return len(shares) }
// Less implements sort.Interface for VaultShares
func (shares VaultShares) Less(i, j int) bool { return shares[i].Denom < shares[j].Denom }

View File

@ -85,7 +85,7 @@ func (vsrs VaultShareRecords) Validate() error {
return nil
}
// NewAllowedVaults returns a new AllowedVaults with the given denom and strategy type.
// NewAllowedVault returns a new AllowedVault with the given values.
func NewAllowedVault(
denom string,
strategyTypes StrategyTypes,