From b5e162a930ae881654d469af295055925461d542 Mon Sep 17 00:00:00 2001 From: Derrick Lee Date: Mon, 12 Sep 2022 09:23:26 -0700 Subject: [PATCH] Update Earn vaults to use sdk.Dec shares (#1283) * Change vault supply to shares * Update deposit shares * Use shares instead of supplied * Update tests, fix share calculation * Pass hard and savings keeper as pointer to earn keeper * Update remaining failing test * Add different share price test, fix comment for share price * Add shares amount to events * Additional share tests, use share to asset conversion for withdraw amount * Update VaultTotalValue test * Use sdk.Dec for vault shares instead of sdk.Int * Add test for expensive 20:1 shares * Update ConvertToShares comment for division, remove redundant test * Add vault share tests --- app/app.go | 4 +- docs/core/proto-docs.md | 72 ++- proto/kava/earn/v1beta1/query.proto | 32 +- proto/kava/earn/v1beta1/tx.proto | 9 +- proto/kava/earn/v1beta1/vault.proto | 31 +- x/earn/client/cli/query.go | 33 -- x/earn/genesis.go | 12 +- x/earn/genesis_test.go | 45 +- x/earn/keeper/deposit.go | 18 +- x/earn/keeper/deposit_test.go | 4 +- x/earn/keeper/grpc_query.go | 308 +++++++------ x/earn/keeper/grpc_query_test.go | 151 ++----- x/earn/keeper/msg_server_test.go | 3 + x/earn/keeper/strategy_hard_test.go | 244 ++++++++++- x/earn/keeper/vault.go | 209 +-------- x/earn/keeper/vault_record.go | 85 ++++ x/earn/keeper/vault_share.go | 82 ++++ x/earn/keeper/vault_share_record.go | 94 ++++ x/earn/keeper/vault_share_record_test.go | 90 ++++ x/earn/keeper/vault_share_test.go | 132 ++++++ x/earn/keeper/vault_test.go | 134 ++---- x/earn/keeper/withdraw.go | 99 +++-- x/earn/keeper/withdraw_test.go | 8 +- x/earn/testutil/suite.go | 26 +- x/earn/types/query.go | 7 - x/earn/types/query.pb.go | 532 ++++------------------- x/earn/types/query.pb.gw.go | 98 ----- x/earn/types/share.go | 383 ++++++++++++++++ x/earn/types/share_test.go | 446 +++++++++++++++++++ x/earn/types/tx.pb.go | 147 ++++++- x/earn/types/vault.go | 39 +- x/earn/types/vault.pb.go | 378 +++++++++++----- x/earn/types/vault_test.go | 74 ++-- 33 files changed, 2477 insertions(+), 1552 deletions(-) create mode 100644 x/earn/keeper/vault_record.go create mode 100644 x/earn/keeper/vault_share.go create mode 100644 x/earn/keeper/vault_share_record.go create mode 100644 x/earn/keeper/vault_share_record_test.go create mode 100644 x/earn/keeper/vault_share_test.go create mode 100644 x/earn/types/share.go create mode 100644 x/earn/types/share_test.go diff --git a/app/app.go b/app/app.go index a63f9fc4..7c0bf157 100644 --- a/app/app.go +++ b/app/app.go @@ -604,8 +604,8 @@ func NewApp( earnSubspace, app.accountKeeper, app.bankKeeper, - hardKeeper, - savingsKeeper, + &hardKeeper, + &savingsKeeper, ) // create committee keeper with router diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index f4e5b06e..3593ff7d 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -184,6 +184,7 @@ - [kava/earn/v1beta1/vault.proto](#kava/earn/v1beta1/vault.proto) - [AllowedVault](#kava.earn.v1beta1.AllowedVault) - [VaultRecord](#kava.earn.v1beta1.VaultRecord) + - [VaultShare](#kava.earn.v1beta1.VaultShare) - [VaultShareRecord](#kava.earn.v1beta1.VaultShareRecord) - [kava/earn/v1beta1/params.proto](#kava/earn/v1beta1/params.proto) @@ -198,8 +199,6 @@ - [QueryDepositsResponse](#kava.earn.v1beta1.QueryDepositsResponse) - [QueryParamsRequest](#kava.earn.v1beta1.QueryParamsRequest) - [QueryParamsResponse](#kava.earn.v1beta1.QueryParamsResponse) - - [QueryTotalDepositedRequest](#kava.earn.v1beta1.QueryTotalDepositedRequest) - - [QueryTotalDepositedResponse](#kava.earn.v1beta1.QueryTotalDepositedResponse) - [QueryVaultsRequest](#kava.earn.v1beta1.QueryVaultsRequest) - [QueryVaultsResponse](#kava.earn.v1beta1.QueryVaultsResponse) - [VaultResponse](#kava.earn.v1beta1.VaultResponse) @@ -2817,14 +2816,28 @@ modified via parameter governance. ### VaultRecord -VaultRecord is the state of a vault and is used to store the state of a -vault. +VaultRecord is the state of a vault. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | -| `denom` | [string](#string) | | Denom is the only supported denomination of the vault for deposits and withdrawals. | -| `total_supply` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | | TotalSupply is the total supply of the vault, denominated **only** in the user deposit/withdrawal denom, must be the same as the Denom field. | +| `total_shares` | [VaultShare](#kava.earn.v1beta1.VaultShare) | | TotalShares is the total distributed number of shares in the vault. | + + + + + + + + +### VaultShare +VaultShare defines shares of a vault owned by a depositor. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `denom` | [string](#string) | | | +| `amount` | [string](#string) | | | @@ -2840,7 +2853,7 @@ VaultShareRecord defines the vault shares owned by a depositor. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `depositor` | [bytes](#bytes) | | Depositor represents the owner of the shares | -| `amount_supplied` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | AmountSupplied represents the total amount a depositor has supplied to the vault. The vault is determined by the coin denom. | +| `shares` | [VaultShare](#kava.earn.v1beta1.VaultShare) | repeated | Shares represent the vault shares owned by the depositor. | @@ -2936,7 +2949,7 @@ DepositResponse defines a deposit query response type. | Field | Type | Label | Description | | ----- | ---- | ----- | ----------- | | `depositor` | [string](#string) | | depositor represents the owner of the deposit. | -| `amount_supplied` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | Amount represents the amount supplied to vaults. | +| `shares` | [VaultShare](#kava.earn.v1beta1.VaultShare) | repeated | Shares represent the issued shares from their corresponding vaults. | | `value` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | Value represents the total accumulated value of denom coins supplied to vaults. This may be greater than or equal to amount_supplied depending on the strategy. | @@ -3002,36 +3015,6 @@ QueryParamsResponse defines the response type for querying x/earn parameters. - - -### QueryTotalDepositedRequest -QueryTotalDepositedRequest is the request type for the Query/TotalDeposited RPC method. - - -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -| `denom` | [string](#string) | | denom represents the vault denom to query total deposited amount for. | - - - - - - - - -### QueryTotalDepositedResponse -QueryTotalDepositedResponse is the response type for the Query/TotalDeposited RPC method. - - -| Field | Type | Label | Description | -| ----- | ---- | ----- | ----------- | -| `supplied_coins` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | | - - - - - - ### QueryVaultsRequest @@ -3072,7 +3055,7 @@ VaultResponse is the response type for a vault. | ----- | ---- | ----- | ----------- | | `denom` | [string](#string) | | denom represents the denom of the vault | | `vault_strategy` | [StrategyType](#kava.earn.v1beta1.StrategyType) | | VaultStrategy is the strategy used for this vault. | -| `total_supplied` | [string](#string) | | TotalSupplied is the total amount of denom coins supplied to the vault. | +| `total_shares` | [string](#string) | | TotalShares is the total amount of shares issued to depositors. | | `total_value` | [string](#string) | | TotalValue is the total value of denom coins supplied to the vault if the vault were to be liquidated. | @@ -3096,7 +3079,6 @@ Query defines the gRPC querier service for earn module | `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| -| `TotalDeposited` | [QueryTotalDepositedRequest](#kava.earn.v1beta1.QueryTotalDepositedRequest) | [QueryTotalDepositedResponse](#kava.earn.v1beta1.QueryTotalDepositedResponse) | TotalDeposited queries total deposited amount for each vault. | GET|/kava/earn/v1beta1/total-deposited/{denom}| @@ -3131,6 +3113,11 @@ MsgDeposit represents a message for depositing assedts into a vault MsgDepositResponse defines the Msg/Deposit response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `shares` | [VaultShare](#kava.earn.v1beta1.VaultShare) | | | + + @@ -3157,6 +3144,11 @@ MsgWithdraw represents a message for withdrawing liquidity from a vault MsgWithdrawResponse defines the Msg/Withdraw response type. +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `shares` | [VaultShare](#kava.earn.v1beta1.VaultShare) | | | + + diff --git a/proto/kava/earn/v1beta1/query.proto b/proto/kava/earn/v1beta1/query.proto index bfe5208c..e13f0be1 100644 --- a/proto/kava/earn/v1beta1/query.proto +++ b/proto/kava/earn/v1beta1/query.proto @@ -11,6 +11,7 @@ import "gogoproto/gogo.proto"; import "google/api/annotations.proto"; import "kava/earn/v1beta1/params.proto"; import "kava/earn/v1beta1/strategy.proto"; +import "kava/earn/v1beta1/vault.proto"; // Query defines the gRPC querier service for earn module service Query { @@ -28,11 +29,6 @@ service Query { rpc Deposits(QueryDepositsRequest) returns (QueryDepositsResponse) { option (google.api.http).get = "/kava/earn/v1beta1/deposits"; } - - // TotalDeposited queries total deposited amount for each vault. - rpc TotalDeposited(QueryTotalDepositedRequest) returns (QueryTotalDepositedResponse) { - option (google.api.http).get = "/kava/earn/v1beta1/total-deposited/{denom}"; - } } // QueryParamsRequest defines the request type for querying x/earn parameters. @@ -40,7 +36,6 @@ message QueryParamsRequest {} // QueryParamsResponse defines the response type for querying x/earn parameters. message QueryParamsResponse { - // params represents the earn module parameters Params params = 1 [(gogoproto.nullable) = false]; } @@ -65,12 +60,8 @@ message VaultResponse { // VaultStrategy is the strategy used for this vault. StrategyType vault_strategy = 2; - // TotalSupplied is the total amount of denom coins supplied to the vault. - string total_supplied = 3 [ - (cosmos_proto.scalar) = "cosmos.Int", - (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", - (gogoproto.nullable) = false - ]; + // TotalShares is the total amount of shares issued to depositors. + string total_shares = 3; // TotalValue is the total value of denom coins supplied to the vault if the // vault were to be liquidated. @@ -107,9 +98,8 @@ message DepositResponse { // depositor represents the owner of the deposit. string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; - // Amount represents the amount supplied to vaults. - repeated cosmos.base.v1beta1.Coin amount_supplied = 2 - [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; + // Shares represent the issued shares from their corresponding vaults. + repeated VaultShare shares = 2 [(gogoproto.castrepeated) = "VaultShares", (gogoproto.nullable) = false]; // Value represents the total accumulated value of denom coins supplied to // vaults. This may be greater than or equal to amount_supplied depending on @@ -117,15 +107,3 @@ message DepositResponse { repeated cosmos.base.v1beta1.Coin value = 3 [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; } - -// QueryTotalDepositedRequest is the request type for the Query/TotalDeposited RPC method. -message QueryTotalDepositedRequest { - // denom represents the vault denom to query total deposited amount for. - string denom = 1; -} - -// QueryTotalDepositedResponse is the response type for the Query/TotalDeposited RPC method. -message QueryTotalDepositedResponse { - repeated cosmos.base.v1beta1.Coin supplied_coins = 1 - [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; -} diff --git a/proto/kava/earn/v1beta1/tx.proto b/proto/kava/earn/v1beta1/tx.proto index 69a4fca5..abafa094 100644 --- a/proto/kava/earn/v1beta1/tx.proto +++ b/proto/kava/earn/v1beta1/tx.proto @@ -4,6 +4,7 @@ package kava.earn.v1beta1; import "cosmos_proto/cosmos.proto"; import "cosmos/base/v1beta1/coin.proto"; import "gogoproto/gogo.proto"; +import "kava/earn/v1beta1/vault.proto"; option go_package = "github.com/kava-labs/kava/x/earn/types"; @@ -27,7 +28,9 @@ message MsgDeposit { } // MsgDepositResponse defines the Msg/Deposit response type. -message MsgDepositResponse {} +message MsgDepositResponse { + VaultShare shares = 1 [(gogoproto.nullable) = false]; +} // MsgWithdraw represents a message for withdrawing liquidity from a vault message MsgWithdraw { @@ -42,4 +45,6 @@ message MsgWithdraw { } // MsgWithdrawResponse defines the Msg/Withdraw response type. -message MsgWithdrawResponse {} \ No newline at end of file +message MsgWithdrawResponse { + VaultShare shares = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/kava/earn/v1beta1/vault.proto b/proto/kava/earn/v1beta1/vault.proto index 04627130..79eed639 100644 --- a/proto/kava/earn/v1beta1/vault.proto +++ b/proto/kava/earn/v1beta1/vault.proto @@ -18,15 +18,10 @@ message AllowedVault { StrategyType vault_strategy = 2; } -// VaultRecord is the state of a vault and is used to store the state of a -// vault. +// VaultRecord is the state of a vault. message VaultRecord { - // Denom is the only supported denomination of the vault for deposits and - // withdrawals. - string denom = 1; - // TotalSupply is the total supply of the vault, denominated **only** in the - // user deposit/withdrawal denom, must be the same as the Denom field. - cosmos.base.v1beta1.Coin total_supply = 2 [(gogoproto.nullable) = false]; + // TotalShares is the total distributed number of shares in the vault. + VaultShare total_shares = 1 [(gogoproto.nullable) = false]; } // VaultShareRecord defines the vault shares owned by a depositor. @@ -36,8 +31,18 @@ message VaultShareRecord { (cosmos_proto.scalar) = "cosmos.AddressBytes", (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; - // AmountSupplied represents the total amount a depositor has supplied to the - // vault. The vault is determined by the coin denom. - repeated cosmos.base.v1beta1.Coin amount_supplied = 2 - [(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false]; -} \ No newline at end of file + // Shares represent the vault shares owned by the depositor. + repeated VaultShare shares = 2 [(gogoproto.castrepeated) = "VaultShares", (gogoproto.nullable) = false]; +} + +// VaultShare defines shares of a vault owned by a depositor. +message VaultShare { + option (gogoproto.goproto_stringer) = false; + + string denom = 1; + string amount = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false + ]; +} diff --git a/x/earn/client/cli/query.go b/x/earn/client/cli/query.go index 9a1601ab..ea3aaf0f 100644 --- a/x/earn/client/cli/query.go +++ b/x/earn/client/cli/query.go @@ -33,7 +33,6 @@ func GetQueryCmd() *cobra.Command { queryParamsCmd(), queryVaultsCmd(), queryDepositsCmd(), - queryTotalDepositedCmd(), } for _, cmd := range cmds { @@ -155,35 +154,3 @@ func queryDepositsCmd() *cobra.Command { return cmd } - -func queryTotalDepositedCmd() *cobra.Command { - return &cobra.Command{ - Use: "total-deposited", - Short: "get the current total deposited amount", - Long: "Get the current total deposited amount in the earn module vaults.", - Args: cobra.MaximumNArgs(1), - Example: fmt.Sprintf(`%[1]s q %[2]s total-deposited -%[1]s q %[2]s total-deposited 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) - - vaultDenom := "" - if len(args) > 1 { - vaultDenom = args[0] - } - - req := types.NewQueryTotalDepositedRequest(vaultDenom) - res, err := queryClient.TotalDeposited(context.Background(), req) - if err != nil { - return err - } - - return clientCtx.PrintProto(res) - }, - } -} diff --git a/x/earn/genesis.go b/x/earn/genesis.go index dd221b63..0fd7212a 100644 --- a/x/earn/genesis.go +++ b/x/earn/genesis.go @@ -21,14 +21,14 @@ func InitGenesis( } // Total of all vault share records, vault record total supply should equal this - vaultTotalSupplies := sdk.NewCoins() + vaultTotalShares := types.NewVaultShares() for _, vaultShareRecord := range gs.VaultShareRecords { if err := vaultShareRecord.Validate(); err != nil { panic(fmt.Sprintf("invalid vault share: %s", err)) } - vaultTotalSupplies = vaultTotalSupplies.Add(vaultShareRecord.AmountSupplied...) + vaultTotalShares = vaultTotalShares.Add(vaultShareRecord.Shares...) k.SetVaultShareRecord(ctx, vaultShareRecord) } @@ -38,12 +38,12 @@ func InitGenesis( panic(fmt.Sprintf("invalid vault record: %s", err)) } - if !vaultRecord.TotalSupply.Amount.Equal(vaultTotalSupplies.AmountOf(vaultRecord.Denom)) { + if !vaultRecord.TotalShares.Amount.Equal(vaultTotalShares.AmountOf(vaultRecord.TotalShares.Denom)) { panic(fmt.Sprintf( "invalid vault record total supply for %s, got %s but sum of vault shares is %s", - vaultRecord.Denom, - vaultRecord.TotalSupply.Amount, - vaultTotalSupplies.AmountOf(vaultRecord.Denom), + vaultRecord.TotalShares.Denom, + vaultRecord.TotalShares.Amount, + vaultTotalShares.AmountOf(vaultRecord.TotalShares.Denom), )) } diff --git a/x/earn/genesis_test.go b/x/earn/genesis_test.go index f33f0954..e52b559e 100644 --- a/x/earn/genesis_test.go +++ b/x/earn/genesis_test.go @@ -25,8 +25,9 @@ func (suite *genesisTestSuite) Test_InitGenesis_ValidationPanic() { }, types.VaultRecords{ { - Denom: "", - TotalSupply: sdk.NewInt64Coin("usdx", 0), + TotalShares: types.VaultShare{ + Denom: "", Amount: sdk.NewDec(1), + }, }, }, types.VaultShareRecords{}, @@ -53,22 +54,26 @@ func (suite *genesisTestSuite) Test_InitAndExportGenesis() { }, types.VaultRecords{ types.VaultRecord{ - Denom: "ukava", - TotalSupply: sdk.NewInt64Coin("ukava", 2000000), + TotalShares: types.NewVaultShare("ukava", sdk.NewDec(3800000)), }, types.VaultRecord{ - Denom: "usdx", - TotalSupply: sdk.NewInt64Coin("usdx", 1000000), + TotalShares: types.NewVaultShare("usdx", sdk.NewDec(1000000)), }, }, types.VaultShareRecords{ types.VaultShareRecord{ - Depositor: depositor_1, - AmountSupplied: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000), sdk.NewInt64Coin("ukava", 1900000)), + Depositor: depositor_1, + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(500000)), + types.NewVaultShare("ukava", sdk.NewDec(1900000)), + ), }, types.VaultShareRecord{ - Depositor: depositor_2, - AmountSupplied: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000), sdk.NewInt64Coin("ukava", 100000)), + Depositor: depositor_2, + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(500000)), + types.NewVaultShare("ukava", sdk.NewDec(1900000)), + ), }, }, ) @@ -107,22 +112,26 @@ func (suite *genesisTestSuite) Test_Marshall() { }, types.VaultRecords{ types.VaultRecord{ - Denom: "ukava", - TotalSupply: sdk.NewInt64Coin("ukava", 2000000), + TotalShares: types.NewVaultShare("ukava", sdk.NewDec(3800000)), }, types.VaultRecord{ - Denom: "usdx", - TotalSupply: sdk.NewInt64Coin("usdx", 1000000), + TotalShares: types.NewVaultShare("usdx", sdk.NewDec(1000000)), }, }, types.VaultShareRecords{ types.VaultShareRecord{ - Depositor: depositor_1, - AmountSupplied: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000), sdk.NewInt64Coin("ukava", 1900000)), + Depositor: depositor_1, + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(500000)), + types.NewVaultShare("ukava", sdk.NewDec(1900000)), + ), }, types.VaultShareRecord{ - Depositor: depositor_2, - AmountSupplied: sdk.NewCoins(sdk.NewInt64Coin("usdx", 500000), sdk.NewInt64Coin("ukava", 100000)), + Depositor: depositor_2, + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(500000)), + types.NewVaultShare("ukava", sdk.NewDec(1900000)), + ), }, }, ) diff --git a/x/earn/keeper/deposit.go b/x/earn/keeper/deposit.go index 541bb206..304febc4 100644 --- a/x/earn/keeper/deposit.go +++ b/x/earn/keeper/deposit.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/earn/types" ) @@ -22,7 +24,7 @@ func (k *Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.C vaultRecord, found := k.GetVaultRecord(ctx, amount.Denom) if !found { // Create a new VaultRecord with 0 supply - vaultRecord = types.NewVaultRecord(amount.Denom) + vaultRecord = types.NewVaultRecord(amount.Denom, sdk.ZeroDec()) } // Get the strategy for the vault @@ -45,14 +47,17 @@ func (k *Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.C vaultShareRecord, found := k.GetVaultShareRecord(ctx, depositor) if !found { // Create a new empty VaultShareRecord with 0 supply - vaultShareRecord = types.NewVaultShareRecord(depositor) + vaultShareRecord = types.NewVaultShareRecord(depositor, types.NewVaultShares()) } - // Increment VaultRecord supply - vaultRecord.TotalSupply = vaultRecord.TotalSupply.Add(amount) + shares, err := k.ConvertToShares(ctx, amount) + if err != nil { + return fmt.Errorf("failed to convert assets to shares: %w", err) + } - // Increment VaultShareRecord supply - vaultShareRecord.AmountSupplied = vaultShareRecord.AmountSupplied.Add(amount) + // Increment VaultRecord total shares and account shares + vaultRecord.TotalShares = vaultRecord.TotalShares.Add(shares) + vaultShareRecord.Shares = vaultShareRecord.Shares.Add(shares) // Update VaultRecord and VaultShareRecord k.SetVaultRecord(ctx, vaultRecord) @@ -68,6 +73,7 @@ func (k *Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.C types.EventTypeVaultDeposit, sdk.NewAttribute(types.AttributeKeyVaultDenom, amount.Denom), sdk.NewAttribute(types.AttributeKeyDepositor, depositor.String()), + sdk.NewAttribute(types.AttributeKeyShares, shares.Amount.String()), sdk.NewAttribute(sdk.AttributeKeyAmount, amount.Amount.String()), ), ) diff --git a/x/earn/keeper/deposit_test.go b/x/earn/keeper/deposit_test.go index 0f5bdc27..7beae143 100644 --- a/x/earn/keeper/deposit_test.go +++ b/x/earn/keeper/deposit_test.go @@ -51,7 +51,9 @@ func (suite *depositTestSuite) TestDeposit_Balances() { ) suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount)) - suite.VaultTotalSuppliedEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalSharesEqual(types.NewVaultShares( + types.NewVaultShare(depositAmount.Denom, depositAmount.Amount.ToDec()), + )) } func (suite *depositTestSuite) TestDeposit_Exceed() { diff --git a/x/earn/keeper/grpc_query.go b/x/earn/keeper/grpc_query.go index c2e50540..1c9c695d 100644 --- a/x/earn/keeper/grpc_query.go +++ b/x/earn/keeper/grpc_query.go @@ -69,10 +69,10 @@ func (s queryServer) Vaults( vaults := []types.VaultResponse{} for _, allowedVault := range queriedAllowedVaults { - totalSupplied, err := s.keeper.GetVaultTotalSupplied(sdkCtx, allowedVault.Denom) - if err != nil { + vaultTotalShares, found := s.keeper.GetVaultTotalShares(sdkCtx, allowedVault.Denom) + if !found { // No supply yet, no error just zero - totalSupplied = sdk.NewCoin(allowedVault.Denom, sdk.ZeroInt()) + vaultTotalShares = types.NewVaultShare(allowedVault.Denom, sdk.ZeroDec()) } totalValue, err := s.keeper.GetVaultTotalValue(sdkCtx, allowedVault.Denom) @@ -83,7 +83,7 @@ func (s queryServer) Vaults( vaults = append(vaults, types.VaultResponse{ Denom: allowedVault.Denom, VaultStrategy: allowedVault.VaultStrategy, - TotalSupplied: totalSupplied.Amount, + TotalShares: vaultTotalShares.Amount.String(), TotalValue: totalValue.Amount, }) } @@ -106,126 +106,159 @@ func (s queryServer) Deposits( // 1. Specific account and specific vault if req.Owner != "" && req.Denom != "" { - owner, err := sdk.AccAddressFromBech32(req.Owner) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid address") - } - - shareRecord, found := s.keeper.GetVaultShareRecord(sdkCtx, owner) - if !found { - return nil, status.Error(codes.NotFound, "No deposit found for owner") - } - - if shareRecord.AmountSupplied.AmountOf(req.Denom).IsZero() { - return nil, status.Error(codes.NotFound, fmt.Sprintf("No deposit for denom %s found for owner", req.Denom)) - } - - value, err := getAccountValue(sdkCtx, s.keeper, owner, shareRecord.AmountSupplied) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - return &types.QueryDepositsResponse{ - Deposits: []types.DepositResponse{ - { - Depositor: owner.String(), - AmountSupplied: shareRecord.AmountSupplied, - Value: value, - }, - }, - Pagination: nil, - }, nil + return s.getAccountVaultDeposit(sdkCtx, req) } // 2. All accounts, specific vault if req.Owner == "" && req.Denom != "" { - _, found := s.keeper.GetVaultRecord(sdkCtx, req.Denom) - if !found { - return nil, status.Error(codes.NotFound, "Vault record for denom not found") - } - - deposits := []types.DepositResponse{} - store := prefix.NewStore(sdkCtx.KVStore(s.keeper.key), types.VaultShareRecordKeyPrefix) - - pageRes, err := query.FilteredPaginate( - store, - req.Pagination, - func(key []byte, value []byte, accumulate bool) (bool, error) { - var record types.VaultShareRecord - err := s.keeper.cdc.Unmarshal(value, &record) - if err != nil { - return false, err - } - - // Only those that have amount of requested denom - if record.AmountSupplied.AmountOf(req.Denom).IsZero() { - // inform paginate that there was no match on this key - return false, nil - } - - if accumulate { - accValue, err := getAccountValue(sdkCtx, s.keeper, record.Depositor, record.AmountSupplied) - if err != nil { - return false, err - } - - // only add to results if paginate tells us to - deposits = append(deposits, types.DepositResponse{ - Depositor: record.Depositor.String(), - AmountSupplied: record.AmountSupplied, - Value: accValue, - }) - } - - // inform paginate that were was a match on this key - return true, nil - }, - ) - - if err != nil { - return nil, err - } - - return &types.QueryDepositsResponse{ - Deposits: deposits, - Pagination: pageRes, - }, nil + return s.getVaultAllDeposits(sdkCtx, req) } // 3. Specific account, all vaults if req.Owner != "" && req.Denom == "" { - owner, err := sdk.AccAddressFromBech32(req.Owner) - if err != nil { - return nil, status.Error(codes.InvalidArgument, "Invalid address") - } - - deposits := []types.DepositResponse{} - - accountShare, found := s.keeper.GetVaultShareRecord(sdkCtx, owner) - if !found { - return nil, status.Error(codes.NotFound, "No deposit found for owner") - } - - value, err := getAccountValue(sdkCtx, s.keeper, owner, accountShare.AmountSupplied) - if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) - } - - deposits = append(deposits, types.DepositResponse{ - Depositor: owner.String(), - AmountSupplied: accountShare.AmountSupplied, - Value: value, - }) - - return &types.QueryDepositsResponse{ - Deposits: deposits, - Pagination: nil, - }, nil + return s.getAccountAllDeposits(sdkCtx, req) } // 4. All accounts, all vaults + return s.getAllDeposits(sdkCtx, req) +} + +// getAccountVaultDeposit returns deposits for a specific vault and a specific +// account +func (s queryServer) getAccountVaultDeposit( + ctx sdk.Context, + req *types.QueryDepositsRequest, +) (*types.QueryDepositsResponse, error) { + owner, err := sdk.AccAddressFromBech32(req.Owner) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Invalid address") + } + + shareRecord, found := s.keeper.GetVaultShareRecord(ctx, owner) + if !found { + return nil, status.Error(codes.NotFound, "No deposit found for owner") + } + + if shareRecord.Shares.AmountOf(req.Denom).IsZero() { + return nil, status.Error(codes.NotFound, fmt.Sprintf("No deposit for denom %s found for owner", req.Denom)) + } + + value, err := getAccountValue(ctx, s.keeper, owner, shareRecord.Shares) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + return &types.QueryDepositsResponse{ + Deposits: []types.DepositResponse{ + { + Depositor: owner.String(), + Shares: shareRecord.Shares, + Value: value, + }, + }, + Pagination: nil, + }, nil +} + +// getVaultAllDeposits returns all deposits for a specific vault +func (s queryServer) getVaultAllDeposits( + ctx sdk.Context, + req *types.QueryDepositsRequest, +) (*types.QueryDepositsResponse, error) { + _, found := s.keeper.GetVaultRecord(ctx, req.Denom) + if !found { + return nil, status.Error(codes.NotFound, "Vault record for denom not found") + } + deposits := []types.DepositResponse{} - store := prefix.NewStore(sdkCtx.KVStore(s.keeper.key), types.VaultShareRecordKeyPrefix) + store := prefix.NewStore(ctx.KVStore(s.keeper.key), types.VaultShareRecordKeyPrefix) + + pageRes, err := query.FilteredPaginate( + store, + req.Pagination, + func(key []byte, value []byte, accumulate bool) (bool, error) { + var record types.VaultShareRecord + err := s.keeper.cdc.Unmarshal(value, &record) + if err != nil { + return false, err + } + + // Only those that have amount of requested denom + if record.Shares.AmountOf(req.Denom).IsZero() { + // inform paginate that there was no match on this key + return false, nil + } + + if accumulate { + accValue, err := getAccountValue(ctx, s.keeper, record.Depositor, record.Shares) + if err != nil { + return false, err + } + + // only add to results if paginate tells us to + deposits = append(deposits, types.DepositResponse{ + Depositor: record.Depositor.String(), + Shares: record.Shares, + Value: accValue, + }) + } + + // inform paginate that were was a match on this key + return true, nil + }, + ) + + if err != nil { + return nil, err + } + + return &types.QueryDepositsResponse{ + Deposits: deposits, + Pagination: pageRes, + }, nil +} + +// getAccountAllDeposits returns deposits for all vaults for a specific account +func (s queryServer) getAccountAllDeposits( + ctx sdk.Context, + req *types.QueryDepositsRequest, +) (*types.QueryDepositsResponse, error) { + owner, err := sdk.AccAddressFromBech32(req.Owner) + if err != nil { + return nil, status.Error(codes.InvalidArgument, "Invalid address") + } + + deposits := []types.DepositResponse{} + + accountShare, found := s.keeper.GetVaultShareRecord(ctx, owner) + if !found { + return nil, status.Error(codes.NotFound, "No deposit found for owner") + } + + value, err := getAccountValue(ctx, s.keeper, owner, accountShare.Shares) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + deposits = append(deposits, types.DepositResponse{ + Depositor: owner.String(), + Shares: accountShare.Shares, + Value: value, + }) + + return &types.QueryDepositsResponse{ + Deposits: deposits, + Pagination: nil, + }, nil +} + +// getAllDeposits returns all deposits for all vaults +func (s queryServer) getAllDeposits( + ctx sdk.Context, + req *types.QueryDepositsRequest, +) (*types.QueryDepositsResponse, error) { + deposits := []types.DepositResponse{} + store := prefix.NewStore(ctx.KVStore(s.keeper.key), types.VaultShareRecordKeyPrefix) pageRes, err := query.Paginate( store, @@ -237,16 +270,16 @@ func (s queryServer) Deposits( return err } - accValue, err := getAccountValue(sdkCtx, s.keeper, record.Depositor, record.AmountSupplied) + accValue, err := getAccountValue(ctx, s.keeper, record.Depositor, record.Shares) if err != nil { return err } // only add to results if paginate tells us to deposits = append(deposits, types.DepositResponse{ - Depositor: record.Depositor.String(), - AmountSupplied: record.AmountSupplied, - Value: accValue, + Depositor: record.Depositor.String(), + Shares: record.Shares, + Value: accValue, }) return nil @@ -263,56 +296,21 @@ func (s queryServer) Deposits( }, nil } -// Deposits implements the gRPC service handler for querying x/earn deposits. -func (s queryServer) TotalDeposited( - ctx context.Context, - req *types.QueryTotalDepositedRequest, -) (*types.QueryTotalDepositedResponse, error) { - if req == nil { - return nil, status.Errorf(codes.InvalidArgument, "empty request") - } - - sdkCtx := sdk.UnwrapSDKContext(ctx) - - // Single vault - if req.Denom != "" { - totalSupplied, err := s.keeper.GetVaultTotalSupplied(sdkCtx, req.Denom) - if err != nil { - return nil, err - } - - return &types.QueryTotalDepositedResponse{ - SuppliedCoins: sdk.NewCoins(totalSupplied), - }, nil - } - - coins := sdk.NewCoins() - vaults := s.keeper.GetAllVaultRecords(sdkCtx) - - for _, vault := range vaults { - coins = coins.Add(vault.TotalSupply) - } - - return &types.QueryTotalDepositedResponse{ - SuppliedCoins: coins, - }, nil -} - func getAccountValue( ctx sdk.Context, keeper Keeper, account sdk.AccAddress, - supplied sdk.Coins, + shares types.VaultShares, ) (sdk.Coins, error) { value := sdk.NewCoins() - for _, coin := range supplied { - accValue, err := keeper.GetVaultAccountValue(ctx, coin.Denom, account) + for _, share := range shares { + accValue, err := keeper.GetVaultAccountValue(ctx, share.Denom, account) if err != nil { return nil, err } - value = value.Add(sdk.NewCoin(coin.Denom, accValue.Amount)) + value = value.Add(sdk.NewCoin(share.Denom, accValue.Amount)) } return value, nil diff --git a/x/earn/keeper/grpc_query_test.go b/x/earn/keeper/grpc_query_test.go index 462c5f4c..65b01a8f 100644 --- a/x/earn/keeper/grpc_query_test.go +++ b/x/earn/keeper/grpc_query_test.go @@ -70,7 +70,7 @@ func (suite *grpcQueryTestSuite) TestVaults_ZeroSupply() { types.VaultResponse{ Denom: "usdx", VaultStrategy: types.STRATEGY_TYPE_HARD, - TotalSupplied: sdk.NewInt(0), + TotalShares: sdk.NewDec(0).String(), TotalValue: sdk.NewInt(0), }, res.Vaults[0], @@ -86,13 +86,13 @@ func (suite *grpcQueryTestSuite) TestVaults_ZeroSupply() { { Denom: "usdx", VaultStrategy: types.STRATEGY_TYPE_HARD, - TotalSupplied: sdk.NewInt(0), + TotalShares: sdk.NewDec(0).String(), TotalValue: sdk.NewInt(0), }, { Denom: "busd", VaultStrategy: types.STRATEGY_TYPE_HARD, - TotalSupplied: sdk.NewInt(0), + TotalShares: sdk.NewDec(0).String(), TotalValue: sdk.NewInt(0), }, }, @@ -121,7 +121,7 @@ func (suite *grpcQueryTestSuite) TestVaults_WithSupply() { types.VaultResponse{ Denom: "usdx", VaultStrategy: types.STRATEGY_TYPE_HARD, - TotalSupplied: depositAmount.Amount, + TotalShares: depositAmount.Amount.ToDec().String(), TotalValue: depositAmount.Amount, }, res.Vaults[0], @@ -183,8 +183,11 @@ func (suite *grpcQueryTestSuite) TestDeposits() { { Depositor: acc1.String(), // Still includes all deposits - AmountSupplied: sdk.NewCoins(deposit1Amount, deposit2Amount), - Value: sdk.NewCoins(deposit1Amount, deposit2Amount), + Shares: types.NewVaultShares( + types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()), + types.NewVaultShare(deposit2Amount.Denom, deposit2Amount.Amount.ToDec()), + ), + Value: sdk.NewCoins(deposit1Amount, deposit2Amount), }, }, res.Deposits, @@ -213,9 +216,12 @@ func (suite *grpcQueryTestSuite) TestDeposits() { suite.Require().ElementsMatch( []types.DepositResponse{ { - Depositor: acc1.String(), - AmountSupplied: sdk.NewCoins(deposit1Amount, deposit2Amount), - Value: sdk.NewCoins(deposit1Amount, deposit2Amount), + Depositor: acc1.String(), + Shares: types.NewVaultShares( + types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()), + types.NewVaultShare(deposit2Amount.Denom, deposit2Amount.Amount.ToDec()), + ), + Value: sdk.NewCoins(deposit1Amount, deposit2Amount), }, }, res.Deposits, @@ -233,9 +239,12 @@ func (suite *grpcQueryTestSuite) TestDeposits() { suite.Require().ElementsMatch( []types.DepositResponse{ { - Depositor: acc2.String(), - AmountSupplied: sdk.NewCoins(deposit1Amount, deposit3Amount), - Value: sdk.NewCoins(deposit1Amount, deposit3Amount), + Depositor: acc2.String(), + Shares: types.NewVaultShares( + types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()), + types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()), + ), + Value: sdk.NewCoins(deposit1Amount, deposit3Amount), }, }, res.Deposits, @@ -253,14 +262,20 @@ func (suite *grpcQueryTestSuite) TestDeposits() { suite.Require().ElementsMatchf( []types.DepositResponse{ { - Depositor: acc1.String(), - AmountSupplied: sdk.NewCoins(deposit1Amount, deposit2Amount), - Value: sdk.NewCoins(deposit1Amount, deposit2Amount), + Depositor: acc1.String(), + Shares: types.NewVaultShares( + types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()), + types.NewVaultShare(deposit2Amount.Denom, deposit2Amount.Amount.ToDec()), + ), + Value: sdk.NewCoins(deposit1Amount, deposit2Amount), }, { - Depositor: acc2.String(), - AmountSupplied: sdk.NewCoins(deposit1Amount, deposit3Amount), - Value: sdk.NewCoins(deposit1Amount, deposit3Amount), + Depositor: acc2.String(), + Shares: types.NewVaultShares( + types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()), + types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()), + ), + Value: sdk.NewCoins(deposit1Amount, deposit3Amount), }, }, res.Deposits, @@ -294,103 +309,3 @@ func (suite *grpcQueryTestSuite) TestDeposits_InvalidAddress() { suite.Require().Error(err) suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "Invalid address")) } - -func (suite *grpcQueryTestSuite) TestTotalDeposited_NoSupply() { - // Add vaults - suite.CreateVault("usdx", types.STRATEGY_TYPE_HARD) - suite.CreateVault("cats", types.STRATEGY_TYPE_HARD) - - res, err := suite.queryClient.TotalDeposited(context.Background(), types.NewQueryTotalDepositedRequest("")) - suite.Require().NoError(err) - suite.Require().True(res.SuppliedCoins.Empty(), "supplied coins should be empty") -} - -func (suite *grpcQueryTestSuite) TestTotalDeposited_All() { - vault1Denom := "usdx" - vault2Denom := "busd" - - // Add vaults - suite.CreateVault(vault1Denom, types.STRATEGY_TYPE_HARD) - suite.CreateVault(vault2Denom, types.STRATEGY_TYPE_HARD) - - startBalance := sdk.NewCoins( - sdk.NewInt64Coin(vault1Denom, 1000), - sdk.NewInt64Coin(vault2Denom, 1000), - ) - deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100) - deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100) - - acc := suite.CreateAccount(startBalance, 0).GetAddress() - err := suite.Keeper.Deposit(suite.Ctx, acc, deposit1Amount) - suite.Require().NoError(err) - - res, err := suite.queryClient.TotalDeposited( - context.Background(), - types.NewQueryTotalDepositedRequest(""), // query all - ) - suite.Require().NoError(err) - suite.Require().Equal( - sdk.NewCoins(deposit1Amount), - res.SuppliedCoins, - "supplied coins should be sum of all supplied coins", - ) - - err = suite.Keeper.Deposit(suite.Ctx, acc, deposit2Amount) - suite.Require().NoError(err) - - res, err = suite.queryClient.TotalDeposited( - context.Background(), - types.NewQueryTotalDepositedRequest(""), // query all - ) - suite.Require().NoError(err) - suite.Require().Equal( - sdk.NewCoins(deposit1Amount, deposit2Amount), - res.SuppliedCoins, - "supplied coins should be sum of all supplied coins for multiple coins", - ) -} - -func (suite *grpcQueryTestSuite) TestTotalDeposited_Single() { - vault1Denom := "usdx" - vault2Denom := "busd" - - // Add vaults - suite.CreateVault(vault1Denom, types.STRATEGY_TYPE_HARD) - suite.CreateVault(vault2Denom, types.STRATEGY_TYPE_HARD) - - startBalance := sdk.NewCoins( - sdk.NewInt64Coin(vault1Denom, 1000), - sdk.NewInt64Coin(vault2Denom, 1000), - ) - deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100) - deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100) - - acc := suite.CreateAccount(startBalance, 0).GetAddress() - err := suite.Keeper.Deposit(suite.Ctx, acc, deposit1Amount) - suite.Require().NoError(err) - - err = suite.Keeper.Deposit(suite.Ctx, acc, deposit2Amount) - suite.Require().NoError(err) - - res, err := suite.queryClient.TotalDeposited( - context.Background(), - types.NewQueryTotalDepositedRequest(vault1Denom), - ) - suite.Require().NoError(err) - suite.Require().Equal( - sdk.NewCoins(deposit1Amount), - res.SuppliedCoins, - "should only contain queried denom", - ) - - res, err = suite.queryClient.TotalDeposited( - context.Background(), - types.NewQueryTotalDepositedRequest(vault2Denom), - ) - suite.Require().NoError(err) - suite.Require().Equal( - sdk.NewCoins(deposit2Amount), - res.SuppliedCoins, - "should only contain queried denom", - ) -} diff --git a/x/earn/keeper/msg_server_test.go b/x/earn/keeper/msg_server_test.go index 0a4ddaaf..0696aebf 100644 --- a/x/earn/keeper/msg_server_test.go +++ b/x/earn/keeper/msg_server_test.go @@ -68,6 +68,8 @@ func (suite *msgServerTestSuite) TestDeposit() { types.EventTypeVaultDeposit, sdk.NewAttribute(types.AttributeKeyVaultDenom, depositAmount.Denom), sdk.NewAttribute(types.AttributeKeyDepositor, acc.GetAddress().String()), + // Shares 1:1 to amount + sdk.NewAttribute(types.AttributeKeyShares, depositAmount.Amount.ToDec().String()), sdk.NewAttribute(sdk.AttributeKeyAmount, depositAmount.Amount.String()), ), ) @@ -120,6 +122,7 @@ func (suite *msgServerTestSuite) TestWithdraw() { types.EventTypeVaultWithdraw, sdk.NewAttribute(types.AttributeKeyVaultDenom, depositAmount.Denom), sdk.NewAttribute(types.AttributeKeyOwner, acc.GetAddress().String()), + sdk.NewAttribute(types.AttributeKeyShares, depositAmount.Amount.ToDec().String()), sdk.NewAttribute(sdk.AttributeKeyAmount, depositAmount.Amount.String()), ), ) diff --git a/x/earn/keeper/strategy_hard_test.go b/x/earn/keeper/strategy_hard_test.go index 774dc18d..1dbd1139 100644 --- a/x/earn/keeper/strategy_hard_test.go +++ b/x/earn/keeper/strategy_hard_test.go @@ -45,7 +45,9 @@ func (suite *strategyHardTestSuite) TestDeposit_SingleAcc() { suite.HardDepositAmountEqual(sdk.NewCoins(depositAmount)) suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount)) - suite.VaultTotalSuppliedEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalSharesEqual(types.NewVaultShares( + types.NewVaultShare(depositAmount.Denom, depositAmount.Amount.ToDec()), + )) // Query vault total totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) @@ -70,10 +72,12 @@ func (suite *strategyHardTestSuite) TestDeposit_SingleAcc_MultipleDeposits() { err = suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) suite.Require().NoError(err) - expectedVaultBalance := sdk.NewCoins(depositAmount.Add(depositAmount)) - suite.HardDepositAmountEqual(expectedVaultBalance) - suite.VaultTotalValuesEqual(expectedVaultBalance) - suite.VaultTotalSuppliedEqual(expectedVaultBalance) + expectedVaultBalance := depositAmount.Add(depositAmount) + suite.HardDepositAmountEqual(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, vaultDenom) @@ -107,7 +111,9 @@ func (suite *strategyHardTestSuite) TestDeposit_MultipleAcc_MultipleDeposits() { suite.HardDepositAmountEqual(sdk.NewCoins(expectedTotalValue)) suite.VaultTotalValuesEqual(sdk.NewCoins(expectedTotalValue)) - suite.VaultTotalSuppliedEqual(sdk.NewCoins(expectedTotalValue)) + suite.VaultTotalSharesEqual(types.NewVaultShares( + types.NewVaultShare(expectedTotalValue.Denom, expectedTotalValue.Amount.ToDec()), + )) // Query vault total totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) @@ -183,7 +189,7 @@ func (suite *strategyHardTestSuite) TestWithdraw() { suite.HardDepositAmountEqual(sdk.NewCoins()) suite.VaultTotalValuesEqual(sdk.NewCoins()) - suite.VaultTotalSuppliedEqual(sdk.NewCoins()) + suite.VaultTotalSharesEqual(types.NewVaultShares()) totalValue, err = suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) suite.Require().NoError(err) @@ -232,26 +238,32 @@ func (suite *strategyHardTestSuite) TestWithdraw_WithAccumulatedHard() { suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) - // Deposits from 2 accounts + // 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) suite.Require().NoError(err) + // Deposit from acc2 so the vault doesn't get deleted when withdrawing + err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount) + 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, 10))) + suite.App.FundModuleAccount(suite.Ctx, types.ModuleName, sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 20))) macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) - suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 10))) + suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 20))) // Query account value accValue, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc) suite.Require().NoError(err) suite.Equal(depositAmount.AddAmount(sdk.NewInt(10)), accValue) - // Withdraw 10, 10 remaining + // Withdraw 100, 10 remaining err = suite.Keeper.Withdraw(suite.Ctx, acc, depositAmount) suite.Require().NoError(err) - // Withdraw again -- too much + // Withdraw 100 again -- too much err = suite.Keeper.Withdraw(suite.Ctx, acc, depositAmount) suite.Require().Error(err) suite.Require().ErrorIs( @@ -268,7 +280,209 @@ func (suite *strategyHardTestSuite) TestWithdraw_WithAccumulatedHard() { err = suite.Keeper.Withdraw(suite.Ctx, acc, sdk.NewCoin(vaultDenom, sdk.NewInt(5))) suite.Require().NoError(err) - _, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc) - suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultRecordNotFound) + accValue, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, 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 usdx not found", err.Error()) +} + +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))) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // 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) + suite.Require().NoError(err) + + acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1) + suite.Require().True(found) + suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(vaultDenom), "initial deposit 1:1 shares") + + // 2. Direct hard deposit from module account to increase vault value + // Total value: 100 -> 110 + macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + err = suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 10))) + suite.Require().NoError(err) + + // 2. acc2 deposit 100 + // share price is 10% more expensive now + // hard 110 -> 210 + err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount) + suite.Require().NoError(err) + + // 100 * 100 / 210 = 47.619047619 shares + // 2.1 price * 47.619047619 = 99.9999999999 + acc2Value, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, 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 = 190.909090909090909091 + // QuoInt64() truncates + expectedAcc2Shares := sdk.NewDec(100).MulInt64(100).QuoInt64(110) + suite.Equal(expectedAcc2Shares, acc2Shares.AmountOf(vaultDenom)) + + vaultTotalShares, found := suite.Keeper.GetVaultTotalShares(suite.Ctx, vaultDenom) + suite.Require().True(found) + suite.Equal(sdk.NewDec(100).Add(expectedAcc2Shares), vaultTotalShares.Amount) + + // 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))) + + // Deposit again from acc1 + err = suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount) + 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(vaultDenom), + "shares should consist of 100 of 1x share price and 63 of 3x share price", + ) +} + +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))) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // 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) + 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) + suite.Require().NoError(err) + + acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1) + suite.Require().True(found) + suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(vaultDenom), "initial deposit 1:1 shares") + + // 2. Direct hard 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.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 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))) + suite.Require().NoError(err) + + _, found = suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1) + suite.Require().False(found, "should have withdrawn entire shares") +} + +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))) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // 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) + 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) + suite.Require().NoError(err) + + acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1) + suite.Require().True(found) + suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(vaultDenom), "initial deposit 1:1 shares") + + // 2. Direct hard 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.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 11))) + suite.Require().NoError(err) + + accBal, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, 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))) + 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, vaultDenom, acc1) + suite.Require().Error(err) +} + +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))) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // Deposit from account1 + acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress() + + // 1. acc1 deposit 100 + err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount) + suite.Require().NoError(err) + + acc1Shares, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc1) + suite.Require().True(found) + suite.Equal(sdk.NewDec(100), acc1Shares.AmountOf(vaultDenom), "initial deposit 1:1 shares") + + // 2. Direct hard 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.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 1900))) + suite.Require().NoError(err) + + accBal, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, 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(vaultDenom, 2000)) + 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, vaultDenom, acc1) + suite.Require().Error(err) } diff --git a/x/earn/keeper/vault.go b/x/earn/keeper/vault.go index 4fba143e..0e51bb16 100644 --- a/x/earn/keeper/vault.go +++ b/x/earn/keeper/vault.go @@ -1,24 +1,25 @@ package keeper import ( - "github.com/cosmos/cosmos-sdk/store/prefix" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/earn/types" ) -// GetVaultTotalSupplied returns the total balance supplied to the vault. This +// 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. -func (k *Keeper) GetVaultTotalSupplied( +func (k *Keeper) GetVaultTotalShares( ctx sdk.Context, denom string, -) (sdk.Coin, error) { +) (types.VaultShare, bool) { vault, found := k.GetVaultRecord(ctx, denom) if !found { - return sdk.Coin{}, types.ErrVaultRecordNotFound + return types.VaultShare{}, false } - return vault.TotalSupply, nil + return vault.TotalShares, true } // GetTotalValue returns the total **value** of all coins in this vault, @@ -47,16 +48,16 @@ func (k *Keeper) GetVaultTotalValue( // GetVaultAccountSupplied returns the supplied amount for a single address // within a vault. -func (k *Keeper) GetVaultAccountSupplied( +func (k *Keeper) GetVaultAccountShares( ctx sdk.Context, acc sdk.AccAddress, -) (sdk.Coins, error) { +) (types.VaultShares, bool) { vaultShareRecord, found := k.GetVaultShareRecord(ctx, acc) if !found { - return sdk.Coins{}, types.ErrVaultShareRecordNotFound + return nil, false } - return vaultShareRecord.AmountSupplied, nil + return vaultShareRecord.Shares, true } // GetVaultAccountValue returns the value of a single address within a vault @@ -66,190 +67,10 @@ func (k *Keeper) GetVaultAccountValue( denom string, acc sdk.AccAddress, ) (sdk.Coin, error) { - totalSupplied, err := k.GetVaultTotalSupplied(ctx, denom) - if err != nil { - return sdk.Coin{}, err + accShares, found := k.GetVaultAccountShares(ctx, acc) + if !found { + return sdk.Coin{}, fmt.Errorf("account vault share record for %s not found", denom) } - accSupplied, err := k.GetVaultAccountSupplied(ctx, acc) - if err != nil { - return sdk.Coin{}, err - } - - vaultTotalValue, err := k.GetVaultTotalValue(ctx, denom) - if err != nil { - return sdk.Coin{}, err - } - - // Percent of vault account ownership = accountSupply / totalSupply - // Value of vault account ownership = percentOwned * totalValue - vaultShare := accSupplied.AmountOf(denom).ToDec().Quo(totalSupplied.Amount.ToDec()) - shareValueDec := vaultTotalValue.Amount.ToDec().Mul(vaultShare) - - return sdk.NewCoin(denom, shareValueDec.TruncateInt()), nil -} - -// ---------------------------------------------------------------------------- -// VaultRecord -- vault total supplies - -// GetVaultRecord returns the vault record for a given denom. -func (k *Keeper) GetVaultRecord( - ctx sdk.Context, - vaultDenom string, -) (types.VaultRecord, bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) - - bz := store.Get(types.VaultKey(vaultDenom)) - if bz == nil { - return types.VaultRecord{}, false - } - - var record types.VaultRecord - k.cdc.MustUnmarshal(bz, &record) - - return record, true -} - -// UpdateVaultRecord updates the vault record in state for a given denom. This -// deletes it if the supply is zero and updates the state if supply is non-zero. -func (k *Keeper) UpdateVaultRecord( - ctx sdk.Context, - vaultRecord types.VaultRecord, -) { - if vaultRecord.TotalSupply.IsZero() { - k.DeleteVaultRecord(ctx, vaultRecord.Denom) - } else { - k.SetVaultRecord(ctx, vaultRecord) - } -} - -// DeleteVaultRecord deletes the vault record for a given denom. -func (k *Keeper) DeleteVaultRecord(ctx sdk.Context, vaultDenom string) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) - store.Delete(types.VaultKey(vaultDenom)) -} - -// SetVaultRecord sets the vault record for a given denom. -func (k *Keeper) SetVaultRecord(ctx sdk.Context, record types.VaultRecord) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) - bz := k.cdc.MustMarshal(&record) - store.Set(types.VaultKey(record.Denom), bz) -} - -// IterateVaultRecords iterates over all vault objects in the store and performs -// a callback function. -func (k Keeper) IterateVaultRecords( - ctx sdk.Context, - cb func(record types.VaultRecord) (stop bool), -) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) - iterator := sdk.KVStorePrefixIterator(store, []byte{}) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - var record types.VaultRecord - k.cdc.MustUnmarshal(iterator.Value(), &record) - if cb(record) { - break - } - } -} - -// GetAllVaultRecords returns all vault records from the store. -func (k Keeper) GetAllVaultRecords(ctx sdk.Context) types.VaultRecords { - var records types.VaultRecords - - k.IterateVaultRecords(ctx, func(record types.VaultRecord) bool { - records = append(records, record) - return false - }) - - return records -} - -// ---------------------------------------------------------------------------- -// VaultShare -- user shares per vault - -// GetVaultShareRecord returns the vault share record for a given denom and -// account. -func (k *Keeper) GetVaultShareRecord( - ctx sdk.Context, - acc sdk.AccAddress, -) (types.VaultShareRecord, bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) - - bz := store.Get(types.DepositorVaultSharesKey(acc)) - if bz == nil { - return types.VaultShareRecord{}, false - } - - var record types.VaultShareRecord - k.cdc.MustUnmarshal(bz, &record) - - return record, true -} - -// UpdateVaultShareRecord updates the vault share record in state for a given -// denom and account. This deletes it if the supply is zero and updates the -// state if supply is non-zero. -func (k *Keeper) UpdateVaultShareRecord( - ctx sdk.Context, - record types.VaultShareRecord, -) { - if record.AmountSupplied.IsZero() { - k.DeleteVaultShareRecord(ctx, record.Depositor) - } else { - k.SetVaultShareRecord(ctx, record) - } -} - -// DeleteVaultShareRecord deletes the vault share record for a given denom and -// account. -func (k *Keeper) DeleteVaultShareRecord( - ctx sdk.Context, - acc sdk.AccAddress, -) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) - store.Delete(types.DepositorVaultSharesKey(acc)) -} - -// SetVaultShareRecord sets the vault share record for a given denom and account. -func (k *Keeper) SetVaultShareRecord( - ctx sdk.Context, - record types.VaultShareRecord, -) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) - bz := k.cdc.MustMarshal(&record) - store.Set(types.DepositorVaultSharesKey(record.Depositor), bz) -} - -// IterateVaultShareRecords iterates over all vault share objects in the store -// and performs a callback function. -func (k Keeper) IterateVaultShareRecords( - ctx sdk.Context, - cb func(record types.VaultShareRecord) (stop bool), -) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) - iterator := sdk.KVStorePrefixIterator(store, []byte{}) - defer iterator.Close() - - for ; iterator.Valid(); iterator.Next() { - var record types.VaultShareRecord - k.cdc.MustUnmarshal(iterator.Value(), &record) - if cb(record) { - break - } - } -} - -// GetAllVaultShareRecords returns all vault share records from the store. -func (k Keeper) GetAllVaultShareRecords(ctx sdk.Context) types.VaultShareRecords { - var records types.VaultShareRecords - - k.IterateVaultShareRecords(ctx, func(record types.VaultShareRecord) bool { - records = append(records, record) - return false - }) - - return records + return k.ConvertToAssets(ctx, accShares.GetShare(denom)) } diff --git a/x/earn/keeper/vault_record.go b/x/earn/keeper/vault_record.go new file mode 100644 index 00000000..9a7eb29d --- /dev/null +++ b/x/earn/keeper/vault_record.go @@ -0,0 +1,85 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/earn/types" +) + +// ---------------------------------------------------------------------------- +// VaultRecord -- vault total shares + +// GetVaultRecord returns the vault record for a given denom. +func (k *Keeper) GetVaultRecord( + ctx sdk.Context, + vaultDenom string, +) (types.VaultRecord, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) + + bz := store.Get(types.VaultKey(vaultDenom)) + if bz == nil { + return types.VaultRecord{}, false + } + + var record types.VaultRecord + k.cdc.MustUnmarshal(bz, &record) + + return record, true +} + +// UpdateVaultRecord updates the vault record in state for a given denom. This +// deletes it if the supply is zero and updates the state if supply is non-zero. +func (k *Keeper) UpdateVaultRecord( + ctx sdk.Context, + vaultRecord types.VaultRecord, +) { + if vaultRecord.TotalShares.Amount.IsZero() { + k.DeleteVaultRecord(ctx, vaultRecord.TotalShares.Denom) + } else { + k.SetVaultRecord(ctx, vaultRecord) + } +} + +// DeleteVaultRecord deletes the vault record for a given denom. +func (k *Keeper) DeleteVaultRecord(ctx sdk.Context, vaultDenom string) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) + store.Delete(types.VaultKey(vaultDenom)) +} + +// SetVaultRecord sets the vault record for a given denom. +func (k *Keeper) SetVaultRecord(ctx sdk.Context, record types.VaultRecord) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) + bz := k.cdc.MustMarshal(&record) + store.Set(types.VaultKey(record.TotalShares.Denom), bz) +} + +// IterateVaultRecords iterates over all vault objects in the store and performs +// a callback function. +func (k Keeper) IterateVaultRecords( + ctx sdk.Context, + cb func(record types.VaultRecord) (stop bool), +) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var record types.VaultRecord + k.cdc.MustUnmarshal(iterator.Value(), &record) + if cb(record) { + break + } + } +} + +// GetAllVaultRecords returns all vault records from the store. +func (k Keeper) GetAllVaultRecords(ctx sdk.Context) types.VaultRecords { + var records types.VaultRecords + + k.IterateVaultRecords(ctx, func(record types.VaultRecord) bool { + records = append(records, record) + return false + }) + + return records +} diff --git a/x/earn/keeper/vault_share.go b/x/earn/keeper/vault_share.go new file mode 100644 index 00000000..c8490c35 --- /dev/null +++ b/x/earn/keeper/vault_share.go @@ -0,0 +1,82 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/earn/types" +) + +// ConvertToShares converts a given amount of tokens to shares. +func (k *Keeper) ConvertToShares(ctx sdk.Context, assets sdk.Coin) (types.VaultShare, error) { + totalShares, found := k.GetVaultTotalShares(ctx, assets.Denom) + if !found { + // No shares issued yet, so shares are issued 1:1 + return types.NewVaultShare(assets.Denom, assets.Amount.ToDec()), nil + } + + totalValue, err := k.GetVaultTotalValue(ctx, assets.Denom) + if err != nil { + return types.VaultShare{}, err + } + + if totalValue.Amount.IsZero() { + return types.VaultShare{}, fmt.Errorf("total value of vault is zero") + } + + // sharePrice = totalValue / totalShares + // issuedShares = assets / sharePrice + // issuedShares = assets / (totalValue / totalShares) + // = assets * (totalShares / totalValue) + // = (assets * totalShares) / totalValue + // + // Multiply by reciprocal of sharePrice to avoid two divisions and limit + // rounding to one time. Per-share price is also not used as there is a loss + // of precision. + + // Division is done at the last step as there is a slight amount that is + // rounded down. + // For example: + // 100 * 100 / 105 == 10000 / 105 == 95.238095238095238095 + // 100 * (100 / 105) == 100 * 0.952380952380952380 == 95.238095238095238000 + // rounded down and truncated ^ loss of precision ^ + issuedShares := assets.Amount.ToDec().Mul(totalShares.Amount).QuoTruncate(totalValue.Amount.ToDec()) + + if issuedShares.IsZero() { + return types.VaultShare{}, fmt.Errorf("share count is zero") + } + + return types.NewVaultShare(assets.Denom, issuedShares), nil +} + +// ConvertToAssets converts a given amount of shares to tokens. +func (k *Keeper) ConvertToAssets(ctx sdk.Context, share types.VaultShare) (sdk.Coin, error) { + totalVaultShares, found := k.GetVaultTotalShares(ctx, share.Denom) + if !found { + return sdk.Coin{}, fmt.Errorf("vault for %s not found", share.Denom) + } + + totalValue, err := k.GetVaultTotalValue(ctx, share.Denom) + if err != nil { + return sdk.Coin{}, err + } + + // percentOwnership := accShares / totalVaultShares + // accValue := totalValue * percentOwnership + // accValue := totalValue * accShares / totalVaultShares + // Division must be last to avoid rounding errors and properly truncate. + value := totalValue.Amount.ToDec().Mul(share.Amount).QuoTruncate(totalVaultShares.Amount) + + return sdk.NewCoin(share.Denom, value.TruncateInt()), nil +} + +// ShareIsDust returns true if the share value is less than 1 coin +func (k *Keeper) ShareIsDust(ctx sdk.Context, share types.VaultShare) (bool, error) { + coin, err := k.ConvertToAssets(ctx, share) + if err != nil { + return false, err + } + + // Truncated int, becomes zero if < 1 + return coin.IsZero(), nil +} diff --git a/x/earn/keeper/vault_share_record.go b/x/earn/keeper/vault_share_record.go new file mode 100644 index 00000000..787e35d5 --- /dev/null +++ b/x/earn/keeper/vault_share_record.go @@ -0,0 +1,94 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/earn/types" +) + +// ---------------------------------------------------------------------------- +// VaultShareRecords -- user shares per vault + +// GetVaultShareRecord returns the vault share record for a given denom and +// account. +func (k *Keeper) GetVaultShareRecord( + ctx sdk.Context, + acc sdk.AccAddress, +) (types.VaultShareRecord, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) + + bz := store.Get(types.DepositorVaultSharesKey(acc)) + if bz == nil { + return types.VaultShareRecord{}, false + } + + var record types.VaultShareRecord + k.cdc.MustUnmarshal(bz, &record) + + return record, true +} + +// UpdateVaultShareRecord updates the vault share record in state for a given +// denom and account. This deletes it if the supply is zero and updates the +// state if supply is non-zero. +func (k *Keeper) UpdateVaultShareRecord( + ctx sdk.Context, + record types.VaultShareRecord, +) { + if record.Shares.IsZero() { + k.DeleteVaultShareRecord(ctx, record.Depositor) + } else { + k.SetVaultShareRecord(ctx, record) + } +} + +// DeleteVaultShareRecord deletes the vault share record for a given denom and +// account. +func (k *Keeper) DeleteVaultShareRecord( + ctx sdk.Context, + acc sdk.AccAddress, +) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) + store.Delete(types.DepositorVaultSharesKey(acc)) +} + +// SetVaultShareRecord sets the vault share record for a given denom and account. +func (k *Keeper) SetVaultShareRecord( + ctx sdk.Context, + record types.VaultShareRecord, +) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) + bz := k.cdc.MustMarshal(&record) + store.Set(types.DepositorVaultSharesKey(record.Depositor), bz) +} + +// IterateVaultShareRecords iterates over all vault share objects in the store +// and performs a callback function. +func (k Keeper) IterateVaultShareRecords( + ctx sdk.Context, + cb func(record types.VaultShareRecord) (stop bool), +) { + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var record types.VaultShareRecord + k.cdc.MustUnmarshal(iterator.Value(), &record) + if cb(record) { + break + } + } +} + +// GetAllVaultShareRecords returns all vault share records from the store. +func (k Keeper) GetAllVaultShareRecords(ctx sdk.Context) types.VaultShareRecords { + var records types.VaultShareRecords + + k.IterateVaultShareRecords(ctx, func(record types.VaultShareRecord) bool { + records = append(records, record) + return false + }) + + return records +} diff --git a/x/earn/keeper/vault_share_record_test.go b/x/earn/keeper/vault_share_record_test.go new file mode 100644 index 00000000..89eb4806 --- /dev/null +++ b/x/earn/keeper/vault_share_record_test.go @@ -0,0 +1,90 @@ +package keeper_test + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/earn/types" +) + +// ---------------------------------------------------------------------------- +// State methods + +func (suite *vaultTestSuite) TestGetVaultRecord() { + record := types.NewVaultRecord("usdx", sdk.ZeroDec()) + + _, found := suite.Keeper.GetVaultRecord(suite.Ctx, record.TotalShares.Denom) + suite.Require().False(found) + + suite.Keeper.SetVaultRecord(suite.Ctx, record) + + stateRecord, found := suite.Keeper.GetVaultRecord(suite.Ctx, record.TotalShares.Denom) + suite.Require().True(found) + suite.Require().Equal(record, stateRecord) +} + +func (suite *vaultTestSuite) TestUpdateVaultRecord() { + record := types.NewVaultRecord("usdx", sdk.ZeroDec()) + + record.TotalShares = types.NewVaultShare("usdx", sdk.NewDec(100)) + + // Update vault + suite.Keeper.UpdateVaultRecord(suite.Ctx, record) + + stateRecord, found := suite.Keeper.GetVaultRecord(suite.Ctx, record.TotalShares.Denom) + suite.Require().True(found, "vault record with supply should exist") + suite.Require().Equal(record, stateRecord) + + // Remove supply + record.TotalShares = types.NewVaultShare("usdx", sdk.NewDec(0)) + suite.Keeper.UpdateVaultRecord(suite.Ctx, record) + + _, found = suite.Keeper.GetVaultRecord(suite.Ctx, record.TotalShares.Denom) + suite.Require().False(found, "vault record with 0 supply should be deleted") +} + +func (suite *vaultTestSuite) TestGetVaultShareRecord() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + record := types.NewVaultShareRecord(acc.GetAddress(), types.NewVaultShares()) + + // Check share doesn't exist before deposit + + _, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) + suite.Require().False(found, "vault share record should not exist before deposit") + + // Update share record + record.Shares = types.NewVaultShares( + types.NewVaultShare(vaultDenom, sdk.NewDec(100)), + ) + suite.Keeper.SetVaultShareRecord(suite.Ctx, record) + + // Check share exists and matches set value + stateRecord, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) + suite.Require().True(found) + suite.Require().Equal(record, stateRecord) +} + +func (suite *vaultTestSuite) TestUpdateVaultShareRecord() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + record := types.NewVaultShareRecord(acc.GetAddress(), types.NewVaultShares( + types.NewVaultShare(vaultDenom, sdk.NewDec(100)), + )) + + // Update vault + suite.Keeper.UpdateVaultShareRecord(suite.Ctx, record) + + stateRecord, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) + suite.Require().True(found, "vault share record with supply should exist") + suite.Require().Equal(record, stateRecord) + + // Remove supply + record.Shares = types.NewVaultShares() + suite.Keeper.UpdateVaultShareRecord(suite.Ctx, record) + + _, found = suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) + suite.Require().False(found, "vault share record with 0 supply should be deleted") +} diff --git a/x/earn/keeper/vault_share_test.go b/x/earn/keeper/vault_share_test.go new file mode 100644 index 00000000..35a46e88 --- /dev/null +++ b/x/earn/keeper/vault_share_test.go @@ -0,0 +1,132 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/x/earn/testutil" + "github.com/kava-labs/kava/x/earn/types" +) + +type vaultShareTestSuite struct { + testutil.Suite +} + +func (suite *vaultShareTestSuite) SetupTest() { + suite.Suite.SetupTest() + suite.Keeper.SetParams(suite.Ctx, types.DefaultParams()) +} + +func TestVaultShareTestSuite(t *testing.T) { + suite.Run(t, new(vaultShareTestSuite)) +} + +func (suite *vaultShareTestSuite) TestConvertToShares() { + vaultDenom := "usdx" + + tests := []struct { + name string + beforeConvert func() + giveAmount sdk.Coin + wantShares types.VaultShare + }{ + { + name: "initial 1:1", + beforeConvert: func() {}, + giveAmount: sdk.NewCoin(vaultDenom, sdk.NewInt(100)), + wantShares: types.NewVaultShare(vaultDenom, sdk.NewDec(100)), + }, + { + name: "value doubled", + + beforeConvert: func() { + // set total shares set total value for hard + // value is double than shares + // shares is 2x price now + suite.addTotalShareAndValue(vaultDenom, sdk.NewDec(100), sdk.NewInt(200)) + }, + giveAmount: sdk.NewCoin(vaultDenom, sdk.NewInt(100)), + wantShares: types.NewVaultShare(vaultDenom, sdk.NewDec(50)), + }, + { + name: "truncate", + + beforeConvert: func() { + suite.addTotalShareAndValue(vaultDenom, sdk.NewDec(1000), sdk.NewInt(1001)) + }, + giveAmount: sdk.NewCoin(vaultDenom, sdk.NewInt(100)), + // 100 * 100 / 101 = 99.0099something + wantShares: types.NewVaultShare(vaultDenom, sdk.NewDec(100).MulInt64(1000).QuoInt64(1001)), + }, + } + + for _, tt := range tests { + suite.Run(tt.name, func() { + // Reset state + suite.Suite.SetupTest() + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + err := suite.App.FundModuleAccount( + suite.Ctx, + types.ModuleName, + sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 10000)), + ) + suite.Require().NoError(err) + + // Run any deposits or any other setup + tt.beforeConvert() + + issuedShares, err := suite.Keeper.ConvertToShares(suite.Ctx, tt.giveAmount) + suite.Require().NoError(err) + + suite.Equal(tt.wantShares, issuedShares) + }) + } +} + +func (suite *vaultShareTestSuite) addTotalShareAndValue( + vaultDenom string, + vaultShares sdk.Dec, + hardDeposit sdk.Int, +) { + macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + + vaultRecord, found := suite.Keeper.GetVaultRecord(suite.Ctx, vaultDenom) + if !found { + vaultRecord = types.NewVaultRecord(vaultDenom, sdk.ZeroDec()) + } + + // Add to vault record + vaultRecord.TotalShares.Amount = vaultRecord.TotalShares.Amount.Add(vaultShares) + + // set total shares + suite.Keeper.UpdateVaultRecord( + suite.Ctx, + vaultRecord, + ) + // add value for hard -- this does not set + err := suite.HardKeeper.Deposit( + suite.Ctx, + macc.GetAddress(), + sdk.NewCoins(sdk.NewCoin(vaultDenom, hardDeposit)), + ) + suite.Require().NoError(err) +} + +func TestPrecisionMulQuoOrder(t *testing.T) { + assetAmount := sdk.NewDec(100) + totalShares := sdk.NewDec(100) + totalValue := sdk.NewDec(105) + + // issuedShares = assetAmount * (totalValue / totalShares) + // = (assetAmount * totalShares) / totalValue + mulFirst := assetAmount.Mul(totalShares).QuoTruncate(totalValue) + quoFirst := assetAmount.Mul(totalShares.QuoTruncate(totalValue)) + + assert.Equal(t, sdk.MustNewDecFromStr("95.238095238095238095"), mulFirst) + assert.Equal(t, sdk.MustNewDecFromStr("95.238095238095238000"), quoFirst) + + assert.NotEqual(t, mulFirst, quoFirst) +} diff --git a/x/earn/keeper/vault_test.go b/x/earn/keeper/vault_test.go index ca84c527..f900024c 100644 --- a/x/earn/keeper/vault_test.go +++ b/x/earn/keeper/vault_test.go @@ -23,7 +23,7 @@ func TestVaultTestSuite(t *testing.T) { suite.Run(t, new(vaultTestSuite)) } -func (suite *vaultTestSuite) TestGetVaultTotalSupplied() { +func (suite *vaultTestSuite) TestGetVaultTotalShares() { vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) @@ -35,22 +35,27 @@ func (suite *vaultTestSuite) TestGetVaultTotalSupplied() { err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) suite.Require().NoError(err) - vaultTotalSupplied, err := suite.Keeper.GetVaultTotalSupplied(suite.Ctx, vaultDenom) - suite.Require().NoError(err) + vaultTotalShares, found := suite.Keeper.GetVaultTotalShares(suite.Ctx, vaultDenom) + suite.Require().True(found) - suite.Equal(depositAmount, vaultTotalSupplied) + suite.Equal(depositAmount.Amount.ToDec(), vaultTotalShares.Amount) } -func (suite *vaultTestSuite) TestGetVaultTotalSupplied_NotFound() { +func (suite *vaultTestSuite) TestGetVaultTotalShares_NotFound() { vaultDenom := "usdx" - _, err := suite.Keeper.GetVaultTotalSupplied(suite.Ctx, vaultDenom) - suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultRecordNotFound) + _, found := suite.Keeper.GetVaultTotalShares(suite.Ctx, vaultDenom) + suite.Require().False(found) } func (suite *vaultTestSuite) TestGetVaultTotalValue() { - // TODO: After strategy implemented GetEstimatedTotalAssets + vaultDenom := "usdx" + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + suite.Equal(sdk.NewInt(0), totalValue.Amount) } func (suite *vaultTestSuite) TestGetVaultTotalValue_NotFound() { @@ -83,17 +88,14 @@ func (suite *vaultTestSuite) TestGetVaultAccountSupplied() { // Before deposit, account supplied is 0 - _, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc1.GetAddress()) - suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultShareRecordNotFound) + _, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc1.GetAddress()) + suite.Require().False(found) - _, err = suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc2.GetAddress()) - suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultShareRecordNotFound) + _, found = suite.Keeper.GetVaultShareRecord(suite.Ctx, acc2.GetAddress()) + suite.Require().False(found) // Deposits from both accounts - - err = suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), deposit1Amount) + err := suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), deposit1Amount) suite.Require().NoError(err) err = suite.Keeper.Deposit(suite.Ctx, acc2.GetAddress(), deposit2Amount) @@ -101,15 +103,15 @@ func (suite *vaultTestSuite) TestGetVaultAccountSupplied() { // Check balances - vaultAcc1Supplied, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc1.GetAddress()) - suite.Require().NoError(err) + vaultAcc1Supplied, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc1.GetAddress()) + suite.Require().True(found) - vaultAcc2Supplied, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc2.GetAddress()) - suite.Require().NoError(err) + vaultAcc2Supplied, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc2.GetAddress()) + suite.Require().True(found) // Account supply only includes the deposit from respective accounts - suite.Equal(sdk.NewCoins(deposit1Amount), vaultAcc1Supplied) - suite.Equal(sdk.NewCoins(deposit1Amount), vaultAcc2Supplied) + suite.Equal(deposit1Amount.Amount.ToDec(), vaultAcc1Supplied.Shares.AmountOf(vaultDenom)) + suite.Equal(deposit1Amount.Amount.ToDec(), vaultAcc2Supplied.Shares.AmountOf(vaultDenom)) } func (suite *vaultTestSuite) TestGetVaultAccountValue() { @@ -135,7 +137,7 @@ func (suite *vaultTestSuite) TestGetVaultAccountValue_VaultNotFound() { _, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc.GetAddress()) suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultRecordNotFound) + suite.Require().Equal("account vault share record for usdx not found", err.Error()) } func (suite *vaultTestSuite) TestGetVaultAccountValue_ShareNotFound() { @@ -155,87 +157,5 @@ func (suite *vaultTestSuite) TestGetVaultAccountValue_ShareNotFound() { // Query from acc2 with no share record _, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc2.GetAddress()) suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultShareRecordNotFound) -} - -// ---------------------------------------------------------------------------- -// State methods - -func (suite *vaultTestSuite) TestGetVaultRecord() { - record := types.NewVaultRecord("usdx") - - _, found := suite.Keeper.GetVaultRecord(suite.Ctx, record.Denom) - suite.Require().False(found) - - suite.Keeper.SetVaultRecord(suite.Ctx, record) - - stateRecord, found := suite.Keeper.GetVaultRecord(suite.Ctx, record.Denom) - suite.Require().True(found) - suite.Require().Equal(record, stateRecord) -} - -func (suite *vaultTestSuite) TestUpdateVaultRecord() { - record := types.NewVaultRecord("usdx") - - record.TotalSupply = sdk.NewInt64Coin("usdx", 100) - - // Update vault - suite.Keeper.UpdateVaultRecord(suite.Ctx, record) - - stateRecord, found := suite.Keeper.GetVaultRecord(suite.Ctx, record.Denom) - suite.Require().True(found, "vault record with supply should exist") - suite.Require().Equal(record, stateRecord) - - // Remove supply - record.TotalSupply = sdk.NewInt64Coin("usdx", 0) - suite.Keeper.UpdateVaultRecord(suite.Ctx, record) - - _, found = suite.Keeper.GetVaultRecord(suite.Ctx, record.Denom) - suite.Require().False(found, "vault record with 0 supply should be deleted") -} - -func (suite *vaultTestSuite) TestGetVaultShareRecord() { - vaultDenom := "usdx" - startBalance := sdk.NewInt64Coin(vaultDenom, 1000) - depositAmount := sdk.NewInt64Coin(vaultDenom, 100) - acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) - - record := types.NewVaultShareRecord(acc.GetAddress()) - - // Check share doesn't exist before deposit - - _, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) - suite.Require().False(found, "vault share record should not exist before deposit") - - // Update share record - record.AmountSupplied = sdk.NewCoins(depositAmount) - suite.Keeper.SetVaultShareRecord(suite.Ctx, record) - - // Check share exists and matches set value - stateRecord, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) - suite.Require().True(found) - suite.Require().Equal(record, stateRecord) -} - -func (suite *vaultTestSuite) TestUpdateVaultShareRecord() { - vaultDenom := "usdx" - startBalance := sdk.NewInt64Coin(vaultDenom, 1000) - depositAmount := sdk.NewInt64Coin(vaultDenom, 100) - acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) - - record := types.NewVaultShareRecord(acc.GetAddress(), depositAmount) - - // Update vault - suite.Keeper.UpdateVaultShareRecord(suite.Ctx, record) - - stateRecord, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) - suite.Require().True(found, "vault share record with supply should exist") - suite.Require().Equal(record, stateRecord) - - // Remove supply - record.AmountSupplied = sdk.NewCoins() - suite.Keeper.UpdateVaultShareRecord(suite.Ctx, record) - - _, found = suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) - suite.Require().False(found, "vault share record with 0 supply should be deleted") + suite.Require().Equal("account vault share record for usdx not found", err.Error()) } diff --git a/x/earn/keeper/withdraw.go b/x/earn/keeper/withdraw.go index 20bdc632..8f7df36a 100644 --- a/x/earn/keeper/withdraw.go +++ b/x/earn/keeper/withdraw.go @@ -28,34 +28,48 @@ func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.C return types.ErrVaultRecordNotFound } - // Get account value for vault - vaultAccValue, err := k.GetVaultAccountValue(ctx, wantAmount.Denom, from) - if err != nil { - return err - } - - if vaultAccValue.IsZero() { - panic("vault account value is zero") - } - // Get account share record for the vault vaultShareRecord, found := k.GetVaultShareRecord(ctx, from) if !found { return types.ErrVaultShareRecordNotFound } - // Percent of vault account value the account is withdrawing - // This is the total account value, not just the supplied amount. - withdrawAmountPercent := wantAmount.Amount.ToDec().Quo(vaultAccValue.Amount.ToDec()) + withdrawShares, err := k.ConvertToShares(ctx, wantAmount) + if err != nil { + return fmt.Errorf("failed to convert assets to shares: %w", err) + } - // Check if account is not withdrawing more than they have - // account value < want withdraw amount - if vaultAccValue.Amount.LT(wantAmount.Amount) { + accCurrentShares := vaultShareRecord.Shares.AmountOf(wantAmount.Denom) + // Check if account is not withdrawing more shares than they have + if accCurrentShares.LT(withdrawShares.Amount) { return sdkerrors.Wrapf( types.ErrInsufficientValue, - "account vault value of %s is less than %s desired withdraw amount", - vaultAccValue, - wantAmount, + "account has less %s vault shares than withdraw shares, %s < %s", + wantAmount.Denom, + accCurrentShares, + withdrawShares.Amount, + ) + } + + // Convert shares to amount to get truncated true share value + withdrawAmount, err := k.ConvertToAssets(ctx, withdrawShares) + if err != nil { + return fmt.Errorf("failed to convert shares to assets: %w", err) + } + + accountValue, err := k.GetVaultAccountValue(ctx, wantAmount.Denom, from) + if err != nil { + return fmt.Errorf("failed to get account value: %w", err) + } + + // Check if withdrawAmount > account value + if withdrawAmount.Amount.GT(accountValue.Amount) { + return sdkerrors.Wrapf( + types.ErrInsufficientValue, + "account has less %s vault value than withdraw amount, %s < %s", + withdrawAmount.Denom, + accountValue.Amount, + withdrawAmount.Amount, ) } @@ -68,8 +82,8 @@ func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.C // Not necessary to check if amount denom is allowed for the strategy, as // there would be no vault record if it weren't allowed. - // Withdraw the wantAmount from the strategy - if err := strategy.Withdraw(ctx, wantAmount); err != nil { + // Withdraw the withdrawAmount from the strategy + if err := strategy.Withdraw(ctx, withdrawAmount); err != nil { return fmt.Errorf("failed to withdraw from strategy: %w", err) } @@ -79,27 +93,33 @@ func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.C ctx, types.ModuleName, from, - sdk.NewCoins(wantAmount), + sdk.NewCoins(withdrawAmount), ); err != nil { return err } - // Shares withdrawn from vault - // For example: - // account supplied = 10hard - // account value = 20hard - // wantAmount = 10hard - // withdrawAmountPercent = 10hard / 20hard = 0.5 - // sharesWithdrawn = 0.5 * 10hard = 5hard - vaultShareAmount := vaultShareRecord.AmountSupplied.AmountOf(wantAmount.Denom) - sharesWithdrawn := sdk.NewCoin(wantAmount.Denom, vaultShareAmount. - ToDec(). - Mul(withdrawAmountPercent). - TruncateInt()) + // Check if new account balance of shares results in account share value + // of < 1 of a sdk.Coin. This share value is not able to be withdrawn and + // should just be removed. + isDust, err := k.ShareIsDust( + ctx, + vaultShareRecord.Shares.GetShare(withdrawAmount.Denom).Sub(withdrawShares), + ) + if err != nil { + return err + } - // Decrement VaultRecord and VaultShareRecord supplies - vaultRecord.TotalSupply = vaultRecord.TotalSupply.Sub(sharesWithdrawn) - vaultShareRecord.AmountSupplied = vaultShareRecord.AmountSupplied.Sub(sdk.NewCoins(sharesWithdrawn)) + if isDust { + // Modify withdrawShares to subtract entire share balance for denom + // This does not modify the actual withdraw coin amount as the + // difference is < 1coin. + withdrawShares = vaultShareRecord.Shares.GetShare(withdrawAmount.Denom) + } + + // Decrement VaultRecord and VaultShareRecord supplies - must delete same + // amounts + vaultShareRecord.Shares = vaultShareRecord.Shares.Sub(withdrawShares) + vaultRecord.TotalShares = vaultRecord.TotalShares.Sub(withdrawShares) // Update VaultRecord and VaultShareRecord, deletes if zero supply k.UpdateVaultRecord(ctx, vaultRecord) @@ -108,9 +128,10 @@ func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.C ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeVaultWithdraw, - sdk.NewAttribute(types.AttributeKeyVaultDenom, wantAmount.Denom), + sdk.NewAttribute(types.AttributeKeyVaultDenom, withdrawAmount.Denom), sdk.NewAttribute(types.AttributeKeyOwner, from.String()), - sdk.NewAttribute(sdk.AttributeKeyAmount, wantAmount.Amount.String()), + sdk.NewAttribute(types.AttributeKeyShares, withdrawShares.Amount.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, withdrawAmount.Amount.String()), ), ) diff --git a/x/earn/keeper/withdraw_test.go b/x/earn/keeper/withdraw_test.go index 7d4401a5..d173d3b7 100644 --- a/x/earn/keeper/withdraw_test.go +++ b/x/earn/keeper/withdraw_test.go @@ -76,7 +76,9 @@ func (suite *withdrawTestSuite) TestWithdraw_NoVaultShareRecord() { ) suite.VaultTotalValuesEqual(sdk.NewCoins(acc1DepositAmount)) - suite.VaultTotalSuppliedEqual(sdk.NewCoins(acc1DepositAmount)) + suite.VaultTotalSharesEqual(types.NewVaultShares( + types.NewVaultShare(acc1DepositAmount.Denom, acc1DepositAmount.Amount.ToDec()), + )) } func (suite *withdrawTestSuite) TestWithdraw_ExceedBalance() { @@ -103,7 +105,9 @@ func (suite *withdrawTestSuite) TestWithdraw_ExceedBalance() { ) suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount)) - suite.VaultTotalSuppliedEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalSharesEqual(types.NewVaultShares( + types.NewVaultShare(depositAmount.Denom, depositAmount.Amount.ToDec()), + )) } func (suite *withdrawTestSuite) TestWithdraw_Zero() { diff --git a/x/earn/testutil/suite.go b/x/earn/testutil/suite.go index 4cf303fd..2b883592 100644 --- a/x/earn/testutil/suite.go +++ b/x/earn/testutil/suite.go @@ -256,21 +256,29 @@ func (suite *Suite) VaultTotalValuesEqual(expected sdk.Coins) { } } -func (suite *Suite) VaultTotalSuppliedEqual(expected sdk.Coins) { - for _, coin := range expected { - vaultBal, err := suite.Keeper.GetVaultTotalSupplied(suite.Ctx, coin.Denom) - suite.Require().NoError(err, "failed to get vault balance") - suite.Require().Equal(coin, vaultBal) +func (suite *Suite) VaultTotalSharesEqual(expected types.VaultShares) { + for _, share := range expected { + vaultBal, found := suite.Keeper.GetVaultTotalShares(suite.Ctx, share.Denom) + suite.Require().Truef(found, "%s vault does not exist", share.Denom) + suite.Require().Equal(share.Amount, vaultBal.Amount) } } -func (suite *Suite) AccountTotalSuppliedEqual(accs []sdk.AccAddress, supplies []sdk.Coins) { +func (suite *Suite) VaultAccountSharesEqual(accs []sdk.AccAddress, supplies []sdk.Coins) { for i, acc := range accs { coins := supplies[i] - accVaultBal, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc) - suite.Require().NoError(err) - suite.Require().True(coins.IsEqual(accVaultBal), "expected account vault balance to equal coins %s, but got %s", coins, accVaultBal) + accVaultBal, found := suite.Keeper.GetVaultAccountShares(suite.Ctx, acc) + suite.Require().True(found) + + for _, coin := range coins { + suite.Require().Equal( + coin.Amount, + accVaultBal.AmountOf(coin.Denom), + "expected account vault balance to equal coins %s, but got %s", + coins, accVaultBal, + ) + } } } diff --git a/x/earn/types/query.go b/x/earn/types/query.go index 8649bd22..1d985f67 100644 --- a/x/earn/types/query.go +++ b/x/earn/types/query.go @@ -26,10 +26,3 @@ func NewQueryDepositsRequest( Pagination: pagination, } } - -// NewQueryTotalDepositedRequest returns a new QueryTotalDepositedRequest -func NewQueryTotalDepositedRequest(denom string) *QueryTotalDepositedRequest { - return &QueryTotalDepositedRequest{ - Denom: denom, - } -} diff --git a/x/earn/types/query.pb.go b/x/earn/types/query.pb.go index 0615b823..57dcf2c3 100644 --- a/x/earn/types/query.pb.go +++ b/x/earn/types/query.pb.go @@ -193,8 +193,8 @@ type VaultResponse struct { Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` // VaultStrategy is the strategy used for this vault. VaultStrategy StrategyType `protobuf:"varint,2,opt,name=vault_strategy,json=vaultStrategy,proto3,enum=kava.earn.v1beta1.StrategyType" json:"vault_strategy,omitempty"` - // TotalSupplied is the total amount of denom coins supplied to the vault. - TotalSupplied github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=total_supplied,json=totalSupplied,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"total_supplied"` + // TotalShares is the total amount of shares issued to depositors. + TotalShares string `protobuf:"bytes,3,opt,name=total_shares,json=totalShares,proto3" json:"total_shares,omitempty"` // TotalValue is the total value of denom coins supplied to the vault if the // vault were to be liquidated. TotalValue github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=total_value,json=totalValue,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"total_value"` @@ -321,8 +321,8 @@ var xxx_messageInfo_QueryDepositsResponse proto.InternalMessageInfo type DepositResponse struct { // depositor represents the owner of the deposit. Depositor string `protobuf:"bytes,1,opt,name=depositor,proto3" json:"depositor,omitempty"` - // Amount represents the amount supplied to vaults. - AmountSupplied github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=amount_supplied,json=amountSupplied,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount_supplied"` + // Shares represent the issued shares from their corresponding vaults. + Shares VaultShares `protobuf:"bytes,2,rep,name=shares,proto3,castrepeated=VaultShares" json:"shares"` // Value represents the total accumulated value of denom coins supplied to // vaults. This may be greater than or equal to amount_supplied depending on // the strategy. @@ -362,83 +362,6 @@ func (m *DepositResponse) XXX_DiscardUnknown() { var xxx_messageInfo_DepositResponse proto.InternalMessageInfo -// QueryTotalDepositedRequest is the request type for the Query/TotalDeposited RPC method. -type QueryTotalDepositedRequest struct { - // denom represents the vault denom to query total deposited amount for. - Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` -} - -func (m *QueryTotalDepositedRequest) Reset() { *m = QueryTotalDepositedRequest{} } -func (m *QueryTotalDepositedRequest) String() string { return proto.CompactTextString(m) } -func (*QueryTotalDepositedRequest) ProtoMessage() {} -func (*QueryTotalDepositedRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_63f8dee2f3192a6b, []int{8} -} -func (m *QueryTotalDepositedRequest) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryTotalDepositedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryTotalDepositedRequest.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 *QueryTotalDepositedRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryTotalDepositedRequest.Merge(m, src) -} -func (m *QueryTotalDepositedRequest) XXX_Size() int { - return m.Size() -} -func (m *QueryTotalDepositedRequest) XXX_DiscardUnknown() { - xxx_messageInfo_QueryTotalDepositedRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryTotalDepositedRequest proto.InternalMessageInfo - -// QueryTotalDepositedResponse is the response type for the Query/TotalDeposited RPC method. -type QueryTotalDepositedResponse struct { - SuppliedCoins github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=supplied_coins,json=suppliedCoins,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"supplied_coins"` -} - -func (m *QueryTotalDepositedResponse) Reset() { *m = QueryTotalDepositedResponse{} } -func (m *QueryTotalDepositedResponse) String() string { return proto.CompactTextString(m) } -func (*QueryTotalDepositedResponse) ProtoMessage() {} -func (*QueryTotalDepositedResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_63f8dee2f3192a6b, []int{9} -} -func (m *QueryTotalDepositedResponse) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *QueryTotalDepositedResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_QueryTotalDepositedResponse.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 *QueryTotalDepositedResponse) XXX_Merge(src proto.Message) { - xxx_messageInfo_QueryTotalDepositedResponse.Merge(m, src) -} -func (m *QueryTotalDepositedResponse) XXX_Size() int { - return m.Size() -} -func (m *QueryTotalDepositedResponse) XXX_DiscardUnknown() { - xxx_messageInfo_QueryTotalDepositedResponse.DiscardUnknown(m) -} - -var xxx_messageInfo_QueryTotalDepositedResponse proto.InternalMessageInfo - func init() { proto.RegisterType((*QueryParamsRequest)(nil), "kava.earn.v1beta1.QueryParamsRequest") proto.RegisterType((*QueryParamsResponse)(nil), "kava.earn.v1beta1.QueryParamsResponse") @@ -448,67 +371,60 @@ func init() { proto.RegisterType((*QueryDepositsRequest)(nil), "kava.earn.v1beta1.QueryDepositsRequest") proto.RegisterType((*QueryDepositsResponse)(nil), "kava.earn.v1beta1.QueryDepositsResponse") proto.RegisterType((*DepositResponse)(nil), "kava.earn.v1beta1.DepositResponse") - proto.RegisterType((*QueryTotalDepositedRequest)(nil), "kava.earn.v1beta1.QueryTotalDepositedRequest") - proto.RegisterType((*QueryTotalDepositedResponse)(nil), "kava.earn.v1beta1.QueryTotalDepositedResponse") } func init() { proto.RegisterFile("kava/earn/v1beta1/query.proto", fileDescriptor_63f8dee2f3192a6b) } var fileDescriptor_63f8dee2f3192a6b = []byte{ - // 833 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x55, 0x4f, 0x6f, 0xd3, 0x48, - 0x14, 0x8f, 0xd3, 0x26, 0x6a, 0x27, 0x4a, 0xaa, 0x9d, 0xcd, 0x4a, 0x49, 0xba, 0x75, 0x52, 0x57, - 0xdb, 0x66, 0xab, 0x8d, 0xbd, 0xcd, 0x4a, 0xbb, 0x97, 0xd5, 0x4a, 0x9b, 0xad, 0x16, 0xf5, 0x82, - 0xc0, 0x2d, 0x3d, 0x20, 0xa1, 0x68, 0x12, 0x8f, 0x8c, 0xd5, 0xc4, 0xe3, 0xda, 0x93, 0x40, 0x41, - 0x5c, 0xfa, 0x09, 0x40, 0x7c, 0x03, 0x40, 0x1c, 0xb8, 0x21, 0xfa, 0x21, 0x7a, 0xac, 0xca, 0x05, - 0x71, 0x28, 0xd0, 0xf2, 0x41, 0x90, 0xe7, 0x4f, 0x52, 0x27, 0x0e, 0xe9, 0xa1, 0xa7, 0x64, 0xe6, - 0xbd, 0xf7, 0xfb, 0xfd, 0xe6, 0xcd, 0x6f, 0x9e, 0xc1, 0xd2, 0x1e, 0xea, 0x23, 0x03, 0x23, 0xdf, - 0x35, 0xfa, 0x1b, 0x2d, 0x4c, 0xd1, 0x86, 0xb1, 0xdf, 0xc3, 0xfe, 0x81, 0xee, 0xf9, 0x84, 0x12, - 0xf8, 0x43, 0x18, 0xd6, 0xc3, 0xb0, 0x2e, 0xc2, 0xa5, 0x62, 0x9b, 0x04, 0x5d, 0x12, 0x34, 0x59, - 0x82, 0xc1, 0x17, 0x3c, 0xbb, 0xb4, 0xce, 0x57, 0x46, 0x0b, 0x05, 0x98, 0xc3, 0x0c, 0x40, 0x3d, - 0x64, 0x3b, 0x2e, 0xa2, 0x0e, 0x71, 0x45, 0xae, 0x7a, 0x39, 0x57, 0x66, 0xb5, 0x89, 0x23, 0xe3, - 0x79, 0x9b, 0xd8, 0x84, 0x73, 0x84, 0xff, 0xc4, 0xee, 0xcf, 0x36, 0x21, 0x76, 0x07, 0x1b, 0xc8, - 0x73, 0x0c, 0xe4, 0xba, 0x84, 0x32, 0x48, 0xc9, 0xaf, 0x8e, 0x1f, 0xc6, 0x43, 0x3e, 0xea, 0xca, - 0x78, 0x65, 0x3c, 0x1e, 0x50, 0x1f, 0x51, 0x6c, 0x8b, 0xf3, 0x6a, 0x79, 0x00, 0x6f, 0x87, 0xba, - 0x6f, 0xb1, 0x32, 0x13, 0xef, 0xf7, 0x70, 0x40, 0xb5, 0x9b, 0xe0, 0xc7, 0xc8, 0x6e, 0xe0, 0x11, - 0x37, 0xc0, 0xf0, 0x2f, 0x90, 0xe6, 0xf0, 0x05, 0xa5, 0xa2, 0x54, 0x33, 0xf5, 0xa2, 0x3e, 0xd6, - 0x2d, 0x9d, 0x97, 0x34, 0x66, 0x8f, 0xcf, 0xca, 0x09, 0x53, 0xa4, 0x6b, 0xeb, 0x82, 0x65, 0x17, - 0xf5, 0x3a, 0x54, 0xb2, 0xc0, 0x3c, 0x48, 0x59, 0xd8, 0x25, 0x5d, 0x86, 0x36, 0x6f, 0xf2, 0x85, - 0x76, 0x47, 0x70, 0xcb, 0x5c, 0xc1, 0xfd, 0x0f, 0x48, 0xf7, 0xd9, 0x4e, 0x41, 0xa9, 0xcc, 0x54, - 0x33, 0xf5, 0x4a, 0x0c, 0x37, 0x2b, 0x91, 0x15, 0x52, 0x02, 0xaf, 0xd2, 0xde, 0x26, 0x41, 0x36, - 0x12, 0x8f, 0xa7, 0x87, 0xff, 0x83, 0x1c, 0xab, 0x68, 0xca, 0x46, 0x15, 0x92, 0x15, 0xa5, 0x9a, - 0xab, 0x97, 0x63, 0xf8, 0xb6, 0x45, 0xca, 0xce, 0x81, 0x87, 0xcd, 0x2c, 0x2b, 0x93, 0x5b, 0xb0, - 0x0d, 0x72, 0x94, 0x50, 0xd4, 0x69, 0x06, 0x3d, 0xcf, 0xeb, 0x38, 0xd8, 0x2a, 0xcc, 0x84, 0x34, - 0x8d, 0xbf, 0x43, 0x55, 0x1f, 0xcf, 0xca, 0xab, 0xb6, 0x43, 0xef, 0xf7, 0x5a, 0x7a, 0x9b, 0x74, - 0x85, 0xa7, 0xc4, 0x4f, 0x2d, 0xb0, 0xf6, 0x0c, 0x7a, 0xe0, 0xe1, 0x40, 0xdf, 0x72, 0xe9, 0xe9, - 0x51, 0x0d, 0x08, 0xcb, 0x6d, 0xb9, 0xd4, 0xcc, 0x32, 0xcc, 0x6d, 0x01, 0x09, 0xef, 0x81, 0x0c, - 0x27, 0xe9, 0xa3, 0x4e, 0x0f, 0x17, 0x66, 0xaf, 0x81, 0x01, 0x30, 0xc0, 0xdd, 0x10, 0x4f, 0x7b, - 0xa5, 0x80, 0x3c, 0xbb, 0x8b, 0x4d, 0xec, 0x91, 0xc0, 0x19, 0xde, 0x9c, 0x0e, 0x52, 0xe4, 0x81, - 0x8b, 0x7d, 0xde, 0xba, 0x46, 0xe1, 0xf4, 0xa8, 0x96, 0x17, 0x18, 0xff, 0x5a, 0x96, 0x8f, 0x83, - 0x60, 0x9b, 0xfa, 0x8e, 0x6b, 0x9b, 0x3c, 0x6d, 0xd8, 0xea, 0x64, 0xb4, 0xd5, 0x60, 0xf8, 0x4a, - 0x58, 0x7b, 0x32, 0xf5, 0x55, 0x5d, 0xe0, 0x84, 0xcf, 0x44, 0xe7, 0x2f, 0x73, 0x68, 0x2d, 0x1b, - 0x0b, 0x05, 0xe6, 0xa5, 0x4a, 0xed, 0xb5, 0x02, 0x7e, 0x1a, 0x91, 0x29, 0xae, 0x78, 0x13, 0xcc, - 0x59, 0x62, 0x4f, 0xd8, 0x46, 0x8b, 0xb9, 0x46, 0x51, 0x36, 0x62, 0x9c, 0x41, 0x25, 0xbc, 0x11, - 0xd1, 0x99, 0x64, 0x3a, 0xd7, 0xa6, 0xea, 0xe4, 0x60, 0x11, 0xa1, 0x2f, 0x92, 0x60, 0x61, 0x84, - 0x0c, 0xfe, 0x09, 0xe6, 0x05, 0x11, 0x99, 0xde, 0xce, 0x61, 0x2a, 0xa4, 0x60, 0x01, 0x75, 0x49, - 0xcf, 0xa5, 0x43, 0x83, 0x25, 0xd9, 0x09, 0x8b, 0x11, 0x65, 0x52, 0xd3, 0x7f, 0xc4, 0x71, 0x1b, - 0xbf, 0x87, 0x07, 0x7b, 0xf3, 0xa9, 0x5c, 0xbd, 0x82, 0x33, 0xc2, 0x82, 0xc0, 0xcc, 0x71, 0x8e, - 0x81, 0xe1, 0x10, 0x48, 0x71, 0xab, 0xcd, 0x5c, 0x3f, 0x17, 0x47, 0xd6, 0xea, 0xa0, 0xc4, 0x2e, - 0x73, 0x27, 0xf4, 0xa1, 0xe8, 0x16, 0xb6, 0xbe, 0x3f, 0x33, 0x9e, 0x29, 0x60, 0x31, 0xb6, 0x48, - 0x34, 0xd9, 0x07, 0x39, 0xd9, 0xa5, 0x66, 0x38, 0x72, 0xa5, 0x1b, 0xae, 0x55, 0x7f, 0x56, 0x52, - 0xb0, 0x65, 0xfd, 0xdd, 0x2c, 0x48, 0x31, 0x4d, 0xf0, 0x11, 0x48, 0xf3, 0xa9, 0x08, 0x7f, 0x89, - 0x71, 0xdf, 0xf8, 0xf8, 0x2d, 0xad, 0x4e, 0x4b, 0xe3, 0xc7, 0xd2, 0x96, 0x0f, 0xdf, 0x7f, 0x7d, - 0x9e, 0x5c, 0x84, 0x45, 0x63, 0xd2, 0x77, 0x00, 0x1e, 0x2a, 0x20, 0xcd, 0x27, 0xe9, 0x64, 0xf2, - 0xc8, 0x54, 0x9e, 0x4c, 0x1e, 0x1d, 0xc8, 0xda, 0xaf, 0x8c, 0x7c, 0x05, 0x2e, 0xc7, 0x90, 0xf3, - 0x99, 0x6b, 0x3c, 0x66, 0xb7, 0xf3, 0x24, 0x14, 0x31, 0x27, 0xdf, 0x26, 0x5c, 0x9b, 0x84, 0x3f, - 0x32, 0x64, 0x4a, 0xd5, 0xe9, 0x89, 0x42, 0xca, 0x0a, 0x93, 0xb2, 0x04, 0x17, 0x63, 0xa4, 0x0c, - 0x5e, 0xf1, 0x4b, 0x05, 0xe4, 0xa2, 0xf6, 0x80, 0xb5, 0x49, 0x0c, 0xb1, 0xde, 0x2b, 0xe9, 0x57, - 0x4d, 0x17, 0xb2, 0xea, 0x4c, 0xd6, 0x6f, 0x70, 0x3d, 0x46, 0x16, 0x9b, 0xb2, 0x35, 0x4b, 0xd6, - 0xc8, 0x56, 0x35, 0x36, 0x8f, 0xbf, 0xa8, 0x89, 0xe3, 0x73, 0x55, 0x39, 0x39, 0x57, 0x95, 0xcf, - 0xe7, 0xaa, 0xf2, 0xf4, 0x42, 0x4d, 0x9c, 0x5c, 0xa8, 0x89, 0x0f, 0x17, 0x6a, 0xe2, 0xee, 0xe5, - 0x91, 0x1e, 0x62, 0xd6, 0x3a, 0xa8, 0x15, 0x70, 0xf4, 0x87, 0x1c, 0x9f, 0x19, 0xb2, 0x95, 0x66, - 0x1f, 0xf7, 0x3f, 0xbe, 0x05, 0x00, 0x00, 0xff, 0xff, 0x3b, 0xb1, 0x1a, 0x34, 0xed, 0x08, 0x00, - 0x00, + // 762 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x55, 0xcf, 0x4f, 0x13, 0x4f, + 0x14, 0xef, 0xb6, 0xb4, 0x81, 0xe9, 0x17, 0xbe, 0x71, 0xa8, 0x49, 0x5b, 0x64, 0x5b, 0x96, 0x08, + 0x95, 0xa4, 0xbb, 0x52, 0x13, 0xbd, 0x18, 0x13, 0x2b, 0xd1, 0xe0, 0xc1, 0xe8, 0xa2, 0x1c, 0x4c, + 0x4c, 0x33, 0xa5, 0x93, 0x65, 0x43, 0xbb, 0xb3, 0xec, 0x4c, 0xab, 0x68, 0xbc, 0x70, 0x37, 0x31, + 0xf1, 0x5f, 0x30, 0x1e, 0x3c, 0xf3, 0x47, 0x70, 0x24, 0x78, 0x31, 0x1e, 0x50, 0xc1, 0xbb, 0x57, + 0x8f, 0x66, 0x7e, 0x2c, 0x65, 0xdb, 0x6d, 0xea, 0xa9, 0x9d, 0xf7, 0xe3, 0xf3, 0xf9, 0xbc, 0x37, + 0x6f, 0xde, 0x82, 0xf9, 0x1d, 0xd4, 0x43, 0x16, 0x46, 0x81, 0x67, 0xf5, 0x56, 0x9b, 0x98, 0xa1, + 0x55, 0x6b, 0xb7, 0x8b, 0x83, 0x3d, 0xd3, 0x0f, 0x08, 0x23, 0xf0, 0x12, 0x77, 0x9b, 0xdc, 0x6d, + 0x2a, 0x77, 0xb1, 0xb0, 0x45, 0x68, 0x87, 0xd0, 0x86, 0x08, 0xb0, 0xe4, 0x41, 0x46, 0x17, 0x57, + 0xe4, 0xc9, 0x6a, 0x22, 0x8a, 0x25, 0xcc, 0x39, 0xa8, 0x8f, 0x1c, 0xd7, 0x43, 0xcc, 0x25, 0x9e, + 0x8a, 0xd5, 0x2f, 0xc6, 0x86, 0x51, 0x5b, 0xc4, 0x0d, 0xfd, 0x39, 0x87, 0x38, 0x44, 0x72, 0xf0, + 0x7f, 0xca, 0x7a, 0xc5, 0x21, 0xc4, 0x69, 0x63, 0x0b, 0xf9, 0xae, 0x85, 0x3c, 0x8f, 0x30, 0x01, + 0x19, 0xf2, 0xeb, 0xc3, 0xc5, 0xf8, 0x28, 0x40, 0x9d, 0xd0, 0x5f, 0x1e, 0xf6, 0x53, 0x16, 0x20, + 0x86, 0x1d, 0x55, 0x6f, 0x31, 0xa6, 0x1d, 0x3d, 0xd4, 0x6d, 0x33, 0xe9, 0x36, 0x72, 0x00, 0x3e, + 0xe1, 0x65, 0x3d, 0x16, 0xa8, 0x36, 0xde, 0xed, 0x62, 0xca, 0x8c, 0x47, 0x60, 0x36, 0x62, 0xa5, + 0x3e, 0xf1, 0x28, 0x86, 0xb7, 0x40, 0x46, 0xb2, 0xe7, 0xb5, 0xb2, 0x56, 0xc9, 0xd6, 0x0a, 0xe6, + 0x50, 0x33, 0x4d, 0x99, 0x52, 0x9f, 0x38, 0x3c, 0x29, 0x25, 0x6c, 0x15, 0x6e, 0xac, 0x28, 0x96, + 0x4d, 0xce, 0x1c, 0xb2, 0xc0, 0x1c, 0x48, 0xb7, 0xb0, 0x47, 0x3a, 0x02, 0x6d, 0xca, 0x96, 0x07, + 0xe3, 0x99, 0xe2, 0x0e, 0x63, 0x15, 0xf7, 0x1d, 0x90, 0x11, 0xba, 0x39, 0x77, 0xaa, 0x92, 0xad, + 0x95, 0x63, 0xb8, 0x45, 0x4a, 0x98, 0x11, 0x4a, 0x90, 0x59, 0xc6, 0x6f, 0x0d, 0x4c, 0x47, 0xfc, + 0xf1, 0xf4, 0xf0, 0x3e, 0x98, 0x11, 0x19, 0x8d, 0xb0, 0x8f, 0xf9, 0x64, 0x59, 0xab, 0xcc, 0xd4, + 0x4a, 0x31, 0x7c, 0x1b, 0x2a, 0xe4, 0xe9, 0x9e, 0x8f, 0xed, 0x69, 0x91, 0x16, 0x9a, 0xe0, 0x02, + 0xf8, 0x8f, 0x11, 0x86, 0xda, 0x0d, 0xba, 0x8d, 0x02, 0x4c, 0xf3, 0x29, 0x41, 0x92, 0x15, 0xb6, + 0x0d, 0x61, 0x82, 0x2f, 0x80, 0x3c, 0x36, 0x7a, 0xa8, 0xdd, 0xc5, 0xf9, 0x09, 0x1e, 0x51, 0xbf, + 0xcd, 0x55, 0x7f, 0x3b, 0x29, 0x2d, 0x39, 0x2e, 0xdb, 0xee, 0x36, 0xcd, 0x2d, 0xd2, 0x51, 0x23, + 0xa9, 0x7e, 0xaa, 0xb4, 0xb5, 0x63, 0xb1, 0x3d, 0x1f, 0x53, 0x73, 0xdd, 0x63, 0xc7, 0x07, 0x55, + 0xa0, 0x26, 0x76, 0xdd, 0x63, 0x36, 0x10, 0x80, 0x9b, 0x1c, 0xcf, 0xf8, 0xa8, 0x81, 0x9c, 0xe8, + 0xe4, 0x1a, 0xf6, 0x09, 0x75, 0xfb, 0x7d, 0x37, 0x41, 0x9a, 0xbc, 0xf4, 0x70, 0x20, 0x0b, 0xaf, + 0xe7, 0x8f, 0x0f, 0xaa, 0x39, 0x85, 0x71, 0xb7, 0xd5, 0x0a, 0x30, 0xa5, 0x1b, 0x2c, 0x70, 0x3d, + 0xc7, 0x96, 0x61, 0xfd, 0x46, 0x25, 0xa3, 0x8d, 0x02, 0xfd, 0x27, 0x20, 0xca, 0xcb, 0xd6, 0x96, + 0x4c, 0x85, 0xc3, 0xdf, 0x80, 0x29, 0x9f, 0x5d, 0x7f, 0x30, 0x1c, 0xac, 0x14, 0xd8, 0x17, 0x32, + 0x8d, 0x4f, 0x1a, 0xb8, 0x3c, 0x20, 0x53, 0x5d, 0xd0, 0x1a, 0x98, 0x6c, 0x29, 0x9b, 0xba, 0x74, + 0x23, 0xe6, 0x12, 0x54, 0xda, 0xc0, 0xb5, 0x9f, 0x67, 0xc2, 0x07, 0x11, 0x9d, 0x49, 0xa1, 0x73, + 0x79, 0xac, 0x4e, 0x09, 0x16, 0x11, 0xfa, 0x47, 0x03, 0xff, 0x0f, 0x90, 0xc1, 0x9b, 0x60, 0x4a, + 0x11, 0x91, 0xf1, 0xed, 0xec, 0x87, 0xc2, 0x87, 0x20, 0xa3, 0xe6, 0x22, 0x29, 0x0a, 0x9b, 0x1f, + 0x35, 0xcd, 0x62, 0x54, 0xea, 0xb3, 0xbc, 0xa6, 0xcf, 0xdf, 0x4b, 0xd9, 0xbe, 0x8d, 0xda, 0x0a, + 0x01, 0x22, 0x90, 0x96, 0x03, 0x94, 0x12, 0x50, 0x85, 0x48, 0x6d, 0x21, 0xd8, 0x3d, 0xe2, 0x7a, + 0xf5, 0xeb, 0x0a, 0xa6, 0xf2, 0x0f, 0xb3, 0xc5, 0x13, 0xa8, 0x2d, 0x91, 0x6b, 0xef, 0x52, 0x20, + 0x2d, 0xee, 0x08, 0xbe, 0x06, 0x19, 0xf9, 0xc2, 0xe1, 0xd5, 0x18, 0xc9, 0xc3, 0xab, 0xa4, 0xb8, + 0x34, 0x2e, 0x4c, 0x76, 0xd2, 0x58, 0xd8, 0xff, 0xf2, 0xeb, 0x43, 0x72, 0x0e, 0x16, 0xac, 0x51, + 0x2b, 0x0f, 0xee, 0x6b, 0x20, 0x23, 0xb7, 0xc2, 0x68, 0xf2, 0xc8, 0x86, 0x19, 0x4d, 0x1e, 0x5d, + 0x2e, 0xc6, 0x35, 0x41, 0xbe, 0x08, 0x17, 0xac, 0x11, 0xdb, 0x92, 0x5a, 0x6f, 0xc4, 0xd4, 0xbf, + 0xe5, 0x22, 0x26, 0xc3, 0x49, 0x85, 0xcb, 0xa3, 0xf0, 0x07, 0x9e, 0x5c, 0xb1, 0x32, 0x3e, 0x50, + 0x49, 0x59, 0x14, 0x52, 0xe6, 0xe1, 0x5c, 0x8c, 0x94, 0x70, 0xa6, 0xeb, 0x6b, 0x87, 0x3f, 0xf5, + 0xc4, 0xe1, 0xa9, 0xae, 0x1d, 0x9d, 0xea, 0xda, 0x8f, 0x53, 0x5d, 0x7b, 0x7f, 0xa6, 0x27, 0x8e, + 0xce, 0xf4, 0xc4, 0xd7, 0x33, 0x3d, 0xf1, 0xfc, 0xe2, 0xea, 0xe0, 0x20, 0xd5, 0x36, 0x6a, 0x52, + 0x09, 0xf7, 0x4a, 0x02, 0x8a, 0x2b, 0x6e, 0x66, 0xc4, 0x27, 0xe0, 0xc6, 0xdf, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x27, 0x75, 0x39, 0xf4, 0x32, 0x07, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -529,8 +445,6 @@ type QueryClient interface { Vaults(ctx context.Context, in *QueryVaultsRequest, opts ...grpc.CallOption) (*QueryVaultsResponse, error) // Deposits queries deposit details based on owner address and vault Deposits(ctx context.Context, in *QueryDepositsRequest, opts ...grpc.CallOption) (*QueryDepositsResponse, error) - // TotalDeposited queries total deposited amount for each vault. - TotalDeposited(ctx context.Context, in *QueryTotalDepositedRequest, opts ...grpc.CallOption) (*QueryTotalDepositedResponse, error) } type queryClient struct { @@ -568,15 +482,6 @@ func (c *queryClient) Deposits(ctx context.Context, in *QueryDepositsRequest, op return out, nil } -func (c *queryClient) TotalDeposited(ctx context.Context, in *QueryTotalDepositedRequest, opts ...grpc.CallOption) (*QueryTotalDepositedResponse, error) { - out := new(QueryTotalDepositedResponse) - err := c.cc.Invoke(ctx, "/kava.earn.v1beta1.Query/TotalDeposited", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // QueryServer is the server API for Query service. type QueryServer interface { // Params queries all parameters of the earn module. @@ -585,8 +490,6 @@ type QueryServer interface { Vaults(context.Context, *QueryVaultsRequest) (*QueryVaultsResponse, error) // Deposits queries deposit details based on owner address and vault Deposits(context.Context, *QueryDepositsRequest) (*QueryDepositsResponse, error) - // TotalDeposited queries total deposited amount for each vault. - TotalDeposited(context.Context, *QueryTotalDepositedRequest) (*QueryTotalDepositedResponse, error) } // UnimplementedQueryServer can be embedded to have forward compatible implementations. @@ -602,9 +505,6 @@ func (*UnimplementedQueryServer) Vaults(ctx context.Context, req *QueryVaultsReq func (*UnimplementedQueryServer) Deposits(ctx context.Context, req *QueryDepositsRequest) (*QueryDepositsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Deposits not implemented") } -func (*UnimplementedQueryServer) TotalDeposited(ctx context.Context, req *QueryTotalDepositedRequest) (*QueryTotalDepositedResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method TotalDeposited not implemented") -} func RegisterQueryServer(s grpc1.Server, srv QueryServer) { s.RegisterService(&_Query_serviceDesc, srv) @@ -664,24 +564,6 @@ func _Query_Deposits_Handler(srv interface{}, ctx context.Context, dec func(inte return interceptor(ctx, in, info, handler) } -func _Query_TotalDeposited_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(QueryTotalDepositedRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(QueryServer).TotalDeposited(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/kava.earn.v1beta1.Query/TotalDeposited", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(QueryServer).TotalDeposited(ctx, req.(*QueryTotalDepositedRequest)) - } - return interceptor(ctx, in, info, handler) -} - var _Query_serviceDesc = grpc.ServiceDesc{ ServiceName: "kava.earn.v1beta1.Query", HandlerType: (*QueryServer)(nil), @@ -698,10 +580,6 @@ var _Query_serviceDesc = grpc.ServiceDesc{ MethodName: "Deposits", Handler: _Query_Deposits_Handler, }, - { - MethodName: "TotalDeposited", - Handler: _Query_TotalDeposited_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "kava/earn/v1beta1/query.proto", @@ -860,16 +738,13 @@ func (m *VaultResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { } i-- dAtA[i] = 0x22 - { - size := m.TotalSupplied.Size() - i -= size - if _, err := m.TotalSupplied.MarshalTo(dAtA[i:]); err != nil { - return 0, err - } - i = encodeVarintQuery(dAtA, i, uint64(size)) + if len(m.TotalShares) > 0 { + i -= len(m.TotalShares) + copy(dAtA[i:], m.TotalShares) + i = encodeVarintQuery(dAtA, i, uint64(len(m.TotalShares))) + i-- + dAtA[i] = 0x1a } - i-- - dAtA[i] = 0x1a if m.VaultStrategy != 0 { i = encodeVarintQuery(dAtA, i, uint64(m.VaultStrategy)) i-- @@ -1017,10 +892,10 @@ func (m *DepositResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x1a } } - if len(m.AmountSupplied) > 0 { - for iNdEx := len(m.AmountSupplied) - 1; iNdEx >= 0; iNdEx-- { + if len(m.Shares) > 0 { + for iNdEx := len(m.Shares) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.AmountSupplied[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Shares[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -1041,73 +916,6 @@ func (m *DepositResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *QueryTotalDepositedRequest) 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 *QueryTotalDepositedRequest) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryTotalDepositedRequest) 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 *QueryTotalDepositedResponse) 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 *QueryTotalDepositedResponse) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *QueryTotalDepositedResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.SuppliedCoins) > 0 { - for iNdEx := len(m.SuppliedCoins) - 1; iNdEx >= 0; iNdEx-- { - { - size, err := m.SuppliedCoins[iNdEx].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 encodeVarintQuery(dAtA []byte, offset int, v uint64) int { offset -= sovQuery(v) base := offset @@ -1180,8 +988,10 @@ func (m *VaultResponse) Size() (n int) { if m.VaultStrategy != 0 { n += 1 + sovQuery(uint64(m.VaultStrategy)) } - l = m.TotalSupplied.Size() - n += 1 + l + sovQuery(uint64(l)) + l = len(m.TotalShares) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } l = m.TotalValue.Size() n += 1 + l + sovQuery(uint64(l)) return n @@ -1237,8 +1047,8 @@ func (m *DepositResponse) Size() (n int) { if l > 0 { n += 1 + l + sovQuery(uint64(l)) } - if len(m.AmountSupplied) > 0 { - for _, e := range m.AmountSupplied { + if len(m.Shares) > 0 { + for _, e := range m.Shares { l = e.Size() n += 1 + l + sovQuery(uint64(l)) } @@ -1252,34 +1062,6 @@ func (m *DepositResponse) Size() (n int) { return n } -func (m *QueryTotalDepositedRequest) 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 *QueryTotalDepositedResponse) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - if len(m.SuppliedCoins) > 0 { - for _, e := range m.SuppliedCoins { - l = e.Size() - n += 1 + l + sovQuery(uint64(l)) - } - } - return n -} - func sovQuery(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1667,7 +1449,7 @@ func (m *VaultResponse) Unmarshal(dAtA []byte) error { } case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TotalSupplied", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TotalShares", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1695,9 +1477,7 @@ func (m *VaultResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.TotalSupplied.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.TotalShares = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { @@ -2087,7 +1867,7 @@ func (m *DepositResponse) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AmountSupplied", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2114,8 +1894,8 @@ func (m *DepositResponse) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AmountSupplied = append(m.AmountSupplied, types.Coin{}) - if err := m.AmountSupplied[len(m.AmountSupplied)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Shares = append(m.Shares, VaultShare{}) + if err := m.Shares[len(m.Shares)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2174,172 +1954,6 @@ func (m *DepositResponse) Unmarshal(dAtA []byte) error { } return nil } -func (m *QueryTotalDepositedRequest) 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: QueryTotalDepositedRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryTotalDepositedRequest: 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 *QueryTotalDepositedResponse) 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: QueryTotalDepositedResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: QueryTotalDepositedResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SuppliedCoins", 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 - } - m.SuppliedCoins = append(m.SuppliedCoins, types.Coin{}) - if err := m.SuppliedCoins[len(m.SuppliedCoins)-1].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 skipQuery(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/earn/types/query.pb.gw.go b/x/earn/types/query.pb.gw.go index a2eb6deb..87ff9831 100644 --- a/x/earn/types/query.pb.gw.go +++ b/x/earn/types/query.pb.gw.go @@ -139,60 +139,6 @@ func local_request_Query_Deposits_0(ctx context.Context, marshaler runtime.Marsh } -func request_Query_TotalDeposited_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryTotalDepositedRequest - 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.TotalDeposited(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) - return msg, metadata, err - -} - -func local_request_Query_TotalDeposited_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq QueryTotalDepositedRequest - 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.TotalDeposited(ctx, &protoReq) - return msg, metadata, err - -} - // RegisterQueryHandlerServer registers the http handlers for service Query to "mux". // UnaryRPC :call QueryServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -259,26 +205,6 @@ func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, serv }) - mux.Handle("GET", pattern_Query_TotalDeposited_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_TotalDeposited_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_TotalDeposited_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - return nil } @@ -380,26 +306,6 @@ func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, clie }) - mux.Handle("GET", pattern_Query_TotalDeposited_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_TotalDeposited_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_TotalDeposited_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) - - }) - return nil } @@ -409,8 +315,6 @@ var ( 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_Deposits_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"kava", "earn", "v1beta1", "deposits"}, "", runtime.AssumeColonVerbOpt(false))) - - pattern_Query_TotalDeposited_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", "total-deposited", "denom"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( @@ -419,6 +323,4 @@ var ( forward_Query_Vaults_0 = runtime.ForwardResponseMessage forward_Query_Deposits_0 = runtime.ForwardResponseMessage - - forward_Query_TotalDeposited_0 = runtime.ForwardResponseMessage ) diff --git a/x/earn/types/share.go b/x/earn/types/share.go new file mode 100644 index 00000000..78275b17 --- /dev/null +++ b/x/earn/types/share.go @@ -0,0 +1,383 @@ +package types + +import ( + fmt "fmt" + "sort" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// NewVaultShare returns a new VaultShare +func NewVaultShare(denom string, amount sdk.Dec) VaultShare { + share := VaultShare{ + Denom: denom, + Amount: amount, + } + + if err := share.Validate(); err != nil { + panic(err) + } + + return share +} + +// Validate returns an error if a VaultShare is invalid. +func (share VaultShare) Validate() error { + if err := sdk.ValidateDenom(share.Denom); err != nil { + return sdkerrors.Wrap(ErrInvalidVaultDenom, err.Error()) + } + + if share.Amount.IsNil() { + return fmt.Errorf("nil share amount: %s", share.Amount) + } + + if share.Amount.IsNegative() { + return fmt.Errorf("vault share amount %v is negative", share.Amount) + } + + return nil +} + +// IsValid returns true if the VaultShare is valid +func (share VaultShare) IsValid() bool { + return share.Validate() == nil +} + +func (share VaultShare) IsPositive() bool { + return share.Amount.IsPositive() +} + +// Add adds amounts of two vault shares with same denom. If the shares differ in +// denom then it panics. +func (share VaultShare) Add(vsB VaultShare) VaultShare { + if share.Denom != vsB.Denom { + panic(fmt.Sprintf("invalid share denominations; %s, %s", share.Denom, vsB.Denom)) + } + + return NewVaultShare(share.Denom, share.Amount.Add(vsB.Amount)) +} + +// IsZero returns if this represents no shares +func (share VaultShare) IsZero() bool { + return share.Amount.IsZero() +} + +// IsNegative returns true if the share amount is negative and false otherwise. +func (share VaultShare) IsNegative() bool { + return share.Amount.IsNegative() +} + +// Sub subtracts amounts of two vault shares with same denom. If the shares +// differ in denom then it panics. +func (share VaultShare) Sub(vsB VaultShare) VaultShare { + if share.Denom != vsB.Denom { + panic(fmt.Sprintf("invalid share denominations; %s, %s", share.Denom, vsB.Denom)) + } + + res := NewVaultShare(share.Denom, share.Amount.Sub(vsB.Amount)) + if res.Amount.IsNegative() { + panic("negative share amount") + } + + return res +} + +func (share VaultShare) String() string { + return fmt.Sprintf("%v%v", share.Amount, share.Denom) +} + +// VaultShares is a slice of VaultShare. +type VaultShares []VaultShare + +// NewVaultShares returns new VaultShares +func NewVaultShares(shares ...VaultShare) VaultShares { + newVaultShares := sanitizeVaultShares(shares) + if err := newVaultShares.Validate(); err != nil { + panic(fmt.Errorf("invalid share set %s: %w", newVaultShares, err)) + } + + return newVaultShares +} + +func sanitizeVaultShares(shares VaultShares) VaultShares { + newVaultShares := removeZeroShares(shares) + if len(newVaultShares) == 0 { + return VaultShares{} + } + + return newVaultShares.Sort() +} + +// Validate returns an error if a slice of VaultShares is invalid. +func (shares VaultShares) Validate() error { + switch len(shares) { + case 0: + return nil + + case 1: + if err := sdk.ValidateDenom(shares[0].Denom); err != nil { + return err + } + if !shares[0].IsPositive() { + return fmt.Errorf("share %s amount is not positive", shares[0]) + } + return nil + default: + // check single share case + if err := (VaultShares{shares[0]}).Validate(); err != nil { + return err + } + + lowDenom := shares[0].Denom + seenDenoms := make(map[string]bool) + seenDenoms[lowDenom] = true + + for _, share := range shares[1:] { + if seenDenoms[share.Denom] { + return fmt.Errorf("duplicate denomination %s", share.Denom) + } + if err := sdk.ValidateDenom(share.Denom); err != nil { + return err + } + if share.Denom <= lowDenom { + return fmt.Errorf("denomination %s is not sorted", share.Denom) + } + if !share.IsPositive() { + return fmt.Errorf("share %s amount is not positive", share.Denom) + } + + // we compare each share against the last denom + lowDenom = share.Denom + seenDenoms[share.Denom] = true + } + + return nil + } +} + +// IsValid returns true if the VaultShares are valid +func (shares VaultShares) IsValid() bool { + return shares.Validate() == nil +} + +// AmountOf returns the amount of shares of the given denom. +func (shares VaultShares) Add(sharesB ...VaultShare) VaultShares { + return shares.safeAdd(sharesB) +} + +// safeAdd will perform addition of two shares sets. If both share sets are +// empty, then an empty set is returned. If only a single set is empty, the +// other set is returned. Otherwise, the shares are compared in order of their +// denomination and addition only occurs when the denominations match, otherwise +// the share is simply added to the sum assuming it's not zero. +// The function panics if `shares` or `sharesB` are not sorted (ascending). +func (shares VaultShares) safeAdd(sharesB VaultShares) VaultShares { + // probably the best way will be to make Shares and interface and hide the structure + // definition (type alias) + if !shares.isSorted() { + panic("Shares (self) must be sorted") + } + if !sharesB.isSorted() { + panic("Wrong argument: shares must be sorted") + } + + sum := (VaultShares)(nil) + indexA, indexB := 0, 0 + lenA, lenB := len(shares), len(sharesB) + + for { + if indexA == lenA { + if indexB == lenB { + // return nil shares if both sets are empty + return sum + } + + // return set B (excluding zero shares) if set A is empty + return append(sum, removeZeroShares(sharesB[indexB:])...) + } else if indexB == lenB { + // return set A (excluding zero shares) if set B is empty + return append(sum, removeZeroShares(shares[indexA:])...) + } + + shareA, shareB := shares[indexA], sharesB[indexB] + + switch strings.Compare(shareA.Denom, shareB.Denom) { + case -1: // share A denom < share B denom + if !shareA.IsZero() { + sum = append(sum, shareA) + } + + indexA++ + + case 0: // share A denom == share B denom + res := shareA.Add(shareB) + if !res.IsZero() { + sum = append(sum, res) + } + + indexA++ + indexB++ + + case 1: // share A denom > share B denom + if !shareB.IsZero() { + sum = append(sum, shareB) + } + + indexB++ + } + } +} + +// Sub subtracts a set of shares from another. +// +// e.g. +// {2A, 3B} - {A} = {A, 3B} +// {2A} - {0B} = {2A} +// {A, B} - {A} = {B} +// +// CONTRACT: Sub will never return Shares where one Share has a non-positive +// amount. In otherwords, IsValid will always return true. +func (shares VaultShares) Sub(sharesB ...VaultShare) VaultShares { + diff, hasNeg := shares.SafeSub(sharesB) + if hasNeg { + panic("negative share amount") + } + + return diff +} + +// SafeSub performs the same arithmetic as Sub but returns a boolean if any +// negative share amount was returned. +// The function panics if `shares` or `sharesB` are not sorted (ascending). +func (shares VaultShares) SafeSub(sharesB VaultShares) (VaultShares, bool) { + diff := shares.safeAdd(sharesB.negative()) + return diff, diff.IsAnyNegative() +} + +// IsAnyNegative returns true if there is at least one share whose amount +// is negative; returns false otherwise. It returns false if the share set +// is empty too. +func (shares VaultShares) IsAnyNegative() bool { + for _, share := range shares { + if share.IsNegative() { + return true + } + } + + return false +} + +// negative returns a set of shares with all amount negative. +func (shares VaultShares) negative() VaultShares { + res := make(VaultShares, 0, len(shares)) + + for _, share := range shares { + res = append(res, VaultShare{ + Denom: share.Denom, + Amount: share.Amount.Neg(), + }) + } + + return res +} + +// AmountOf returns the amount of shares of the given denom. +func (v VaultShares) AmountOf(denom string) sdk.Dec { + for _, s := range v { + if s.Denom == denom { + return s.Amount + } + } + + return sdk.ZeroDec() +} + +// GetShare the single share of the given denom. +func (v VaultShares) GetShare(denom string) VaultShare { + for _, s := range v { + if s.Denom == denom { + return s + } + } + + return NewVaultShare(denom, sdk.ZeroDec()) +} + +// IsZero returns true if the VaultShares is empty. +func (v VaultShares) IsZero() bool { + for _, s := range v { + // If any amount is non-zero, false + if !s.Amount.IsZero() { + return false + } + } + + return true +} + +func (shares VaultShares) isSorted() bool { + for i := 1; i < len(shares); i++ { + if shares[i-1].Denom > shares[i].Denom { + return false + } + } + return true +} + +func (shares VaultShares) String() string { + if len(shares) == 0 { + return "" + } + + out := "" + for _, share := range shares { + out += fmt.Sprintf("%v,", share.String()) + } + + return out[:len(out)-1] +} + +// removeZeroShares removes all zero shares from the given share set in-place. +func removeZeroShares(shares VaultShares) VaultShares { + for i := 0; i < len(shares); i++ { + if shares[i].IsZero() { + break + } else if i == len(shares)-1 { + return shares + } + } + + var result VaultShares + if len(shares) > 0 { + result = make(VaultShares, 0, len(shares)-1) + } + + for _, share := range shares { + if !share.IsZero() { + result = append(result, share) + } + } + + return result +} + +// ---------------------------------------------------------------------------- +// VaultShares sort interface + +func (a VaultShares) Len() int { return len(a) } + +// Less implements sort.Interface for VaultShares +func (shares VaultShares) Less(i, j int) bool { return shares[i].Denom < shares[j].Denom } + +// Swap implements sort.Interface for VaultShares +func (shares VaultShares) Swap(i, j int) { shares[i], shares[j] = shares[j], shares[i] } + +var _ sort.Interface = VaultShares{} + +// Sort is a helper function to sort the set of vault shares in-place +func (shares VaultShares) Sort() VaultShares { + sort.Sort(shares) + return shares +} diff --git a/x/earn/types/share_test.go b/x/earn/types/share_test.go new file mode 100644 index 00000000..08341736 --- /dev/null +++ b/x/earn/types/share_test.go @@ -0,0 +1,446 @@ +package types_test + +import ( + "strings" + "testing" + + "github.com/kava-labs/kava/x/earn/types" + "github.com/stretchr/testify/suite" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + testDenom1 = "ukava" + testDenom2 = "usdx" +) + +func d(i int64) sdk.Dec { + return sdk.NewDec(i) +} + +type vaultShareTestSuite struct { + suite.Suite +} + +func TestVaultShareTestSuite(t *testing.T) { + suite.Run(t, new(vaultShareTestSuite)) +} + +func (s *vaultShareTestSuite) TestNewVaultShareFromDec() { + s.Require().NotPanics(func() { + types.NewVaultShare(testDenom1, sdk.NewDec(5)) + }) + s.Require().NotPanics(func() { + types.NewVaultShare(testDenom1, sdk.ZeroDec()) + }) + s.Require().NotPanics(func() { + types.NewVaultShare(strings.ToUpper(testDenom1), sdk.NewDec(5)) + }) + s.Require().Panics(func() { + types.NewVaultShare(testDenom1, sdk.NewDec(-5)) + }) +} + +func (s *vaultShareTestSuite) TestAddVaultShare() { + vaultShareA1 := types.NewVaultShare(testDenom1, sdk.NewDecWithPrec(11, 1)) + vaultShareA2 := types.NewVaultShare(testDenom1, sdk.NewDecWithPrec(22, 1)) + vaultShareB1 := types.NewVaultShare(testDenom2, sdk.NewDecWithPrec(11, 1)) + + // regular add + res := vaultShareA1.Add(vaultShareA1) + s.Require().Equal(vaultShareA2, res, "sum of shares is incorrect") + + // bad denom add + s.Require().Panics(func() { + vaultShareA1.Add(vaultShareB1) + }, "expected panic on sum of different denoms") +} + +func (s *vaultShareTestSuite) TestAddVaultShares() { + one := sdk.NewDec(1) + zero := sdk.NewDec(0) + two := sdk.NewDec(2) + + cases := []struct { + inputOne types.VaultShares + inputTwo types.VaultShares + expected types.VaultShares + }{ + { + types.VaultShares{ + {testDenom1, one}, + {testDenom2, one}, + }, + types.VaultShares{ + {testDenom1, one}, + {testDenom2, one}, + }, + types.VaultShares{ + {testDenom1, two}, + {testDenom2, two}, + }, + }, + { + types.VaultShares{ + {testDenom1, zero}, + {testDenom2, one}, + }, + types.VaultShares{ + {testDenom1, zero}, + {testDenom2, zero}, + }, + types.VaultShares{ + {testDenom2, one}, + }, + }, + { + types.VaultShares{ + {testDenom1, zero}, + {testDenom2, zero}, + }, + types.VaultShares{ + {testDenom1, zero}, + {testDenom2, zero}, + }, + types.VaultShares(nil), + }, + } + + for tcIndex, tc := range cases { + res := tc.inputOne.Add(tc.inputTwo...) + s.Require().Equal(tc.expected, res, "sum of shares is incorrect, tc #%d", tcIndex) + } +} + +func (s *vaultShareTestSuite) TestFilteredZeroVaultShares() { + cases := []struct { + name string + input types.VaultShares + original string + expected string + }{ + { + name: "all greater than zero", + input: types.VaultShares{ + {"testa", sdk.NewDec(1)}, + {"testb", sdk.NewDec(2)}, + {"testc", sdk.NewDec(3)}, + {"testd", sdk.NewDec(4)}, + {"teste", sdk.NewDec(5)}, + }, + original: "1.000000000000000000testa,2.000000000000000000testb,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", + expected: "1.000000000000000000testa,2.000000000000000000testb,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", + }, + { + name: "zero share in middle", + input: types.VaultShares{ + {"testa", sdk.NewDec(1)}, + {"testb", sdk.NewDec(2)}, + {"testc", sdk.NewDec(0)}, + {"testd", sdk.NewDec(4)}, + {"teste", sdk.NewDec(5)}, + }, + original: "1.000000000000000000testa,2.000000000000000000testb,0.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", + expected: "1.000000000000000000testa,2.000000000000000000testb,4.000000000000000000testd,5.000000000000000000teste", + }, + { + name: "zero share end (unordered)", + input: types.VaultShares{ + {"teste", sdk.NewDec(5)}, + {"testc", sdk.NewDec(3)}, + {"testa", sdk.NewDec(1)}, + {"testd", sdk.NewDec(4)}, + {"testb", sdk.NewDec(0)}, + }, + original: "5.000000000000000000teste,3.000000000000000000testc,1.000000000000000000testa,4.000000000000000000testd,0.000000000000000000testb", + expected: "1.000000000000000000testa,3.000000000000000000testc,4.000000000000000000testd,5.000000000000000000teste", + }, + } + + for _, tt := range cases { + undertest := types.NewVaultShares(tt.input...) + s.Require().Equal(tt.expected, undertest.String(), "NewVaultShares must return expected results") + s.Require().Equal(tt.original, tt.input.String(), "input must be unmodified and match original") + } +} + +func (s *vaultShareTestSuite) TestIsValid() { + tests := []struct { + share types.VaultShare + expectPass bool + msg string + }{ + { + types.NewVaultShare("mytoken", sdk.NewDec(10)), + true, + "valid shares should have passed", + }, + { + types.VaultShare{Denom: "BTC", Amount: sdk.NewDec(10)}, + true, + "valid uppercase denom", + }, + { + types.VaultShare{Denom: "Bitshare", Amount: sdk.NewDec(10)}, + true, + "valid mixed case denom", + }, + { + types.VaultShare{Denom: "btc", Amount: sdk.NewDec(-10)}, + false, + "negative amount", + }, + } + + for _, tc := range tests { + tc := tc + if tc.expectPass { + s.Require().True(tc.share.IsValid(), tc.msg) + } else { + s.Require().False(tc.share.IsValid(), tc.msg) + } + } +} + +func (s *vaultShareTestSuite) TestSubVaultShare() { + tests := []struct { + share types.VaultShare + expectPass bool + msg string + }{ + { + types.NewVaultShare("mytoken", sdk.NewDec(20)), + true, + "valid shares should have passed", + }, + { + types.NewVaultShare("othertoken", sdk.NewDec(20)), + false, + "denom mismatch", + }, + { + types.NewVaultShare("mytoken", sdk.NewDec(9)), + false, + "negative amount", + }, + } + + vaultShare := types.NewVaultShare("mytoken", sdk.NewDec(10)) + + for _, tc := range tests { + tc := tc + if tc.expectPass { + equal := tc.share.Sub(vaultShare) + s.Require().Equal(equal, vaultShare, tc.msg) + } else { + s.Require().Panics(func() { tc.share.Sub(vaultShare) }, tc.msg) + } + } +} + +func (s *vaultShareTestSuite) TestSubVaultShares() { + tests := []struct { + shares types.VaultShares + expectPass bool + msg string + }{ + { + types.NewVaultShares(types.NewVaultShare("mytoken", d(10)), types.NewVaultShare("btc", d(20)), types.NewVaultShare("eth", d(30))), + true, + "sorted shares should have passed", + }, + { + types.VaultShares{types.NewVaultShare("mytoken", d(10)), types.NewVaultShare("btc", d(20)), types.NewVaultShare("eth", d(30))}, + false, + "unorted shares should panic", + }, + { + types.VaultShares{types.VaultShare{Denom: "BTC", Amount: sdk.NewDec(10)}, types.NewVaultShare("eth", d(15)), types.NewVaultShare("mytoken", d(5))}, + false, + "invalid denoms", + }, + } + + vaultShares := types.NewVaultShares(types.NewVaultShare("btc", d(10)), types.NewVaultShare("eth", d(15)), types.NewVaultShare("mytoken", d(5))) + + for _, tc := range tests { + tc := tc + if tc.expectPass { + equal := tc.shares.Sub(vaultShares...) + s.Require().Equal(equal, vaultShares, tc.msg) + } else { + s.Require().Panics(func() { tc.shares.Sub(vaultShares...) }, tc.msg) + } + } +} + +func (s *vaultShareTestSuite) TestSortVaultShares() { + good := types.VaultShares{ + types.NewVaultShare("gas", d(1)), + types.NewVaultShare("mineral", d(1)), + types.NewVaultShare("tree", d(1)), + } + empty := types.VaultShares{ + types.NewVaultShare("gold", d(0)), + } + badSort1 := types.VaultShares{ + types.NewVaultShare("tree", d(1)), + types.NewVaultShare("gas", d(1)), + types.NewVaultShare("mineral", d(1)), + } + badSort2 := types.VaultShares{ // both are after the first one, but the second and third are in the wrong order + types.NewVaultShare("gas", d(1)), + types.NewVaultShare("tree", d(1)), + types.NewVaultShare("mineral", d(1)), + } + badAmt := types.VaultShares{ + types.NewVaultShare("gas", d(1)), + types.NewVaultShare("tree", d(0)), + types.NewVaultShare("mineral", d(1)), + } + dup := types.VaultShares{ + types.NewVaultShare("gas", d(1)), + types.NewVaultShare("gas", d(1)), + types.NewVaultShare("mineral", d(1)), + } + cases := []struct { + name string + shares types.VaultShares + before, after bool // valid before/after sort + }{ + {"valid shares", good, true, true}, + {"empty shares", empty, false, false}, + {"unsorted shares (1)", badSort1, false, true}, + {"unsorted shares (2)", badSort2, false, true}, + {"zero amount shares", badAmt, false, false}, + {"duplicate shares", dup, false, false}, + } + + for _, tc := range cases { + s.Require().Equal(tc.before, tc.shares.IsValid(), "share validity is incorrect before sorting; %s", tc.name) + tc.shares.Sort() + s.Require().Equal(tc.after, tc.shares.IsValid(), "share validity is incorrect after sorting; %s", tc.name) + } +} + +func (s *vaultShareTestSuite) TestVaultSharesValidate() { + testCases := []struct { + input types.VaultShares + expectedPass bool + }{ + {types.VaultShares{}, true}, + {types.VaultShares{types.VaultShare{testDenom1, sdk.NewDec(5)}}, true}, + {types.VaultShares{types.VaultShare{testDenom1, sdk.NewDec(5)}, types.VaultShare{testDenom2, sdk.NewDec(100000)}}, true}, + {types.VaultShares{types.VaultShare{testDenom1, sdk.NewDec(-5)}}, false}, + {types.VaultShares{types.VaultShare{"BTC", sdk.NewDec(5)}}, true}, + {types.VaultShares{types.VaultShare{"0BTC", sdk.NewDec(5)}}, false}, + {types.VaultShares{types.VaultShare{testDenom1, sdk.NewDec(5)}, types.VaultShare{"B", sdk.NewDec(100000)}}, false}, + {types.VaultShares{types.VaultShare{testDenom1, sdk.NewDec(5)}, types.VaultShare{testDenom2, sdk.NewDec(-100000)}}, false}, + {types.VaultShares{types.VaultShare{testDenom1, sdk.NewDec(-5)}, types.VaultShare{testDenom2, sdk.NewDec(100000)}}, false}, + {types.VaultShares{types.VaultShare{"BTC", sdk.NewDec(5)}, types.VaultShare{testDenom2, sdk.NewDec(100000)}}, true}, + {types.VaultShares{types.VaultShare{"0BTC", sdk.NewDec(5)}, types.VaultShare{testDenom2, sdk.NewDec(100000)}}, false}, + } + + for i, tc := range testCases { + err := tc.input.Validate() + if tc.expectedPass { + s.Require().NoError(err, "unexpected result for test case #%d, input: %v", i, tc.input) + } else { + s.Require().Error(err, "unexpected result for test case #%d, input: %v", i, tc.input) + } + } +} + +func (s *vaultShareTestSuite) TestVaultSharesString() { + testCases := []struct { + input types.VaultShares + expected string + }{ + {types.VaultShares{}, ""}, + { + types.VaultShares{ + types.NewVaultShare("atom", sdk.NewDecWithPrec(5040000000000000000, sdk.Precision)), + types.NewVaultShare("stake", sdk.NewDecWithPrec(4000000000000000, sdk.Precision)), + }, + "5.040000000000000000atom,0.004000000000000000stake", + }, + } + + for i, tc := range testCases { + out := tc.input.String() + s.Require().Equal(tc.expected, out, "unexpected result for test case #%d, input: %v", i, tc.input) + } +} + +func (s *vaultShareTestSuite) TestNewVaultSharesWithIsValid() { + fake1 := append(types.NewVaultShares(types.NewVaultShare("mytoken", d(10))), types.VaultShare{Denom: "10BTC", Amount: sdk.NewDec(10)}) + fake2 := append(types.NewVaultShares(types.NewVaultShare("mytoken", d(10))), types.VaultShare{Denom: "BTC", Amount: sdk.NewDec(-10)}) + + tests := []struct { + share types.VaultShares + expectPass bool + msg string + }{ + { + types.NewVaultShares(types.NewVaultShare("mytoken", d(10))), + true, + "valid shares should have passed", + }, + { + fake1, + false, + "invalid denoms", + }, + { + fake2, + false, + "negative amount", + }, + } + + for _, tc := range tests { + tc := tc + if tc.expectPass { + s.Require().True(tc.share.IsValid(), tc.msg) + } else { + s.Require().False(tc.share.IsValid(), tc.msg) + } + } +} + +func (s *vaultShareTestSuite) TestVaultShares_AddVaultShareWithIsValid() { + lengthTestVaultShares := types.NewVaultShares().Add(types.NewVaultShare("mytoken", d(10))).Add(types.VaultShare{Denom: "BTC", Amount: sdk.NewDec(10)}) + s.Require().Equal(2, len(lengthTestVaultShares), "should be 2") + + tests := []struct { + share types.VaultShares + expectPass bool + msg string + }{ + { + types.NewVaultShares().Add(types.NewVaultShare("mytoken", d(10))), + true, + "valid shares should have passed", + }, + { + types.NewVaultShares().Add(types.NewVaultShare("mytoken", d(10))).Add(types.VaultShare{Denom: "0BTC", Amount: sdk.NewDec(10)}), + false, + "invalid denoms", + }, + { + types.NewVaultShares().Add(types.NewVaultShare("mytoken", d(10))).Add(types.VaultShare{Denom: "BTC", Amount: sdk.NewDec(-10)}), + false, + "negative amount", + }, + } + + for _, tc := range tests { + tc := tc + if tc.expectPass { + s.Require().True(tc.share.IsValid(), tc.msg) + } else { + s.Require().False(tc.share.IsValid(), tc.msg) + } + } +} diff --git a/x/earn/types/tx.pb.go b/x/earn/types/tx.pb.go index 221b019b..911f0e38 100644 --- a/x/earn/types/tx.pb.go +++ b/x/earn/types/tx.pb.go @@ -74,6 +74,7 @@ var xxx_messageInfo_MsgDeposit proto.InternalMessageInfo // MsgDepositResponse defines the Msg/Deposit response type. type MsgDepositResponse struct { + Shares VaultShare `protobuf:"bytes,1,opt,name=shares,proto3" json:"shares"` } func (m *MsgDepositResponse) Reset() { *m = MsgDepositResponse{} } @@ -109,6 +110,13 @@ func (m *MsgDepositResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgDepositResponse proto.InternalMessageInfo +func (m *MsgDepositResponse) GetShares() VaultShare { + if m != nil { + return m.Shares + } + return VaultShare{} +} + // MsgWithdraw represents a message for withdrawing liquidity from a vault type MsgWithdraw struct { // from represents the address we are withdrawing for @@ -153,6 +161,7 @@ var xxx_messageInfo_MsgWithdraw proto.InternalMessageInfo // MsgWithdrawResponse defines the Msg/Withdraw response type. type MsgWithdrawResponse struct { + Shares VaultShare `protobuf:"bytes,1,opt,name=shares,proto3" json:"shares"` } func (m *MsgWithdrawResponse) Reset() { *m = MsgWithdrawResponse{} } @@ -188,6 +197,13 @@ func (m *MsgWithdrawResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgWithdrawResponse proto.InternalMessageInfo +func (m *MsgWithdrawResponse) GetShares() VaultShare { + if m != nil { + return m.Shares + } + return VaultShare{} +} + func init() { proto.RegisterType((*MsgDeposit)(nil), "kava.earn.v1beta1.MsgDeposit") proto.RegisterType((*MsgDepositResponse)(nil), "kava.earn.v1beta1.MsgDepositResponse") @@ -198,30 +214,33 @@ func init() { func init() { proto.RegisterFile("kava/earn/v1beta1/tx.proto", fileDescriptor_2e9dcf48a3fa0009) } var fileDescriptor_2e9dcf48a3fa0009 = []byte{ - // 367 bytes of a gzipped FileDescriptorProto + // 404 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xca, 0x4e, 0x2c, 0x4b, 0xd4, 0x4f, 0x4d, 0x2c, 0xca, 0xd3, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x04, 0xc9, 0xe9, 0x81, 0xe4, 0xf4, 0xa0, 0x72, 0x52, 0x92, 0xc9, 0xf9, 0xc5, 0xb9, 0xf9, 0xc5, 0xf1, 0x60, 0x05, 0xfa, 0x10, 0x0e, 0x44, 0xb5, 0x94, 0x1c, 0x84, 0xa7, 0x9f, 0x94, 0x58, 0x9c, 0x0a, 0x37, 0x2b, 0x39, 0x3f, 0x33, 0x0f, 0x2a, - 0x2f, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0xd1, 0x07, 0x62, 0x41, 0x44, 0x95, 0x9a, 0x19, 0xb9, 0xb8, - 0x7c, 0x8b, 0xd3, 0x5d, 0x52, 0x0b, 0xf2, 0x8b, 0x33, 0x4b, 0x84, 0xcc, 0xb8, 0x38, 0x53, 0x20, - 0xcc, 0xfc, 0x22, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0x89, 0x4b, 0x5b, 0x74, 0x45, 0xa0, - 0x36, 0x39, 0xa6, 0xa4, 0x14, 0xa5, 0x16, 0x17, 0x07, 0x97, 0x14, 0x65, 0xe6, 0xa5, 0x07, 0x21, - 0x94, 0x0a, 0x99, 0x73, 0xb1, 0x25, 0xe6, 0xe6, 0x97, 0xe6, 0x95, 0x48, 0x30, 0x29, 0x30, 0x6a, - 0x70, 0x1b, 0x49, 0xea, 0x41, 0x75, 0x80, 0x5c, 0x03, 0x73, 0xbd, 0x9e, 0x73, 0x7e, 0x66, 0x9e, - 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, 0xe5, 0x56, 0x2c, 0x1d, 0x0b, 0xe4, 0x19, 0x94, - 0x44, 0xb8, 0x84, 0x10, 0x8e, 0x08, 0x4a, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x55, 0xaa, 0xe2, - 0xe2, 0xf6, 0x2d, 0x4e, 0x0f, 0xcf, 0x2c, 0xc9, 0x48, 0x29, 0x4a, 0x2c, 0x17, 0xd2, 0xe1, 0x62, - 0x49, 0x2b, 0xca, 0xcf, 0x25, 0xe8, 0x2c, 0xb0, 0x2a, 0x4a, 0x5d, 0x24, 0xca, 0x25, 0x8c, 0x64, - 0x37, 0xcc, 0x49, 0x46, 0xab, 0x18, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x85, 0xfc, 0xb9, 0xd8, 0x61, - 0x41, 0x26, 0xab, 0x87, 0x11, 0x4d, 0x7a, 0x08, 0xcf, 0x48, 0xa9, 0xe2, 0x95, 0x86, 0x19, 0x2c, - 0x14, 0xc4, 0xc5, 0x01, 0xf7, 0xa8, 0x1c, 0x76, 0x2d, 0x30, 0x79, 0x29, 0x35, 0xfc, 0xf2, 0x30, - 0x33, 0x9d, 0x1c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, - 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x2d, 0x3d, - 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x64, 0x96, 0x6e, 0x4e, 0x62, 0x52, - 0x31, 0x98, 0xa5, 0x5f, 0x01, 0x49, 0x8c, 0x25, 0x95, 0x05, 0xa9, 0xc5, 0x49, 0x6c, 0xe0, 0x44, - 0x62, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x48, 0xd0, 0x39, 0xa6, 0x02, 0x00, 0x00, + 0x2f, 0x92, 0x9e, 0x9f, 0x9e, 0x0f, 0xd1, 0x07, 0x62, 0x41, 0x45, 0x65, 0x31, 0xed, 0x2f, 0x4b, + 0x2c, 0xcd, 0x29, 0x81, 0x48, 0x2b, 0x35, 0x33, 0x72, 0x71, 0xf9, 0x16, 0xa7, 0xbb, 0xa4, 0x16, + 0xe4, 0x17, 0x67, 0x96, 0x08, 0x99, 0x71, 0x71, 0xa6, 0x40, 0x98, 0xf9, 0x45, 0x12, 0x8c, 0x0a, + 0x8c, 0x1a, 0x9c, 0x4e, 0x12, 0x97, 0xb6, 0xe8, 0x8a, 0x40, 0x1d, 0xe2, 0x98, 0x92, 0x52, 0x94, + 0x5a, 0x5c, 0x1c, 0x5c, 0x52, 0x94, 0x99, 0x97, 0x1e, 0x84, 0x50, 0x2a, 0x64, 0xce, 0xc5, 0x96, + 0x98, 0x9b, 0x5f, 0x9a, 0x57, 0x22, 0xc1, 0xa4, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0xa9, 0x07, 0xd5, + 0x01, 0x72, 0x2c, 0xcc, 0x73, 0x7a, 0xce, 0xf9, 0x99, 0x79, 0x4e, 0x2c, 0x27, 0xee, 0xc9, 0x33, + 0x04, 0x41, 0x95, 0x5b, 0xb1, 0x74, 0x2c, 0x90, 0x67, 0x50, 0x0a, 0xe4, 0x12, 0x42, 0x38, 0x22, + 0x28, 0xb5, 0xb8, 0x20, 0x3f, 0xaf, 0x38, 0x55, 0xc8, 0x9a, 0x8b, 0xad, 0x38, 0x23, 0xb1, 0x28, + 0xb5, 0x18, 0xec, 0x12, 0x6e, 0x23, 0x59, 0x3d, 0x8c, 0xf0, 0xd2, 0x0b, 0x03, 0xf9, 0x25, 0x18, + 0xa4, 0x0a, 0x66, 0x30, 0x44, 0x8b, 0x52, 0x15, 0x17, 0xb7, 0x6f, 0x71, 0x7a, 0x78, 0x66, 0x49, + 0x46, 0x4a, 0x51, 0x62, 0xb9, 0x90, 0x0e, 0x17, 0x4b, 0x5a, 0x51, 0x7e, 0x2e, 0x41, 0x3f, 0x81, + 0x55, 0x51, 0xea, 0x9d, 0x20, 0x2e, 0x61, 0x24, 0xbb, 0xa9, 0xe2, 0x1f, 0xa3, 0x55, 0x8c, 0x5c, + 0xcc, 0xbe, 0xc5, 0xe9, 0x42, 0xfe, 0x5c, 0xec, 0xb0, 0xc8, 0xc2, 0xa6, 0x1f, 0x11, 0x8c, 0x52, + 0xaa, 0x78, 0xa5, 0xe1, 0xae, 0x0a, 0xe2, 0xe2, 0x80, 0x87, 0x92, 0x1c, 0x76, 0x2d, 0x30, 0x79, + 0x29, 0x35, 0xfc, 0xf2, 0x30, 0x33, 0x9d, 0x1c, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, + 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, + 0x8e, 0x21, 0x4a, 0x2d, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0x64, + 0x96, 0x6e, 0x4e, 0x62, 0x52, 0x31, 0x98, 0xa5, 0x5f, 0x01, 0x49, 0xa5, 0x25, 0x95, 0x05, 0xa9, + 0xc5, 0x49, 0x6c, 0xe0, 0xe4, 0x69, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xb4, 0xb0, 0x19, 0x18, + 0x3f, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -404,6 +423,16 @@ func (m *MsgDepositResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.Shares.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -467,6 +496,16 @@ func (m *MsgWithdrawResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.Shares.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -502,6 +541,8 @@ func (m *MsgDepositResponse) Size() (n int) { } var l int _ = l + l = m.Shares.Size() + n += 1 + l + sovTx(uint64(l)) return n } @@ -526,6 +567,8 @@ func (m *MsgWithdrawResponse) Size() (n int) { } var l int _ = l + l = m.Shares.Size() + n += 1 + l + sovTx(uint64(l)) return n } @@ -679,6 +722,39 @@ func (m *MsgDepositResponse) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: MsgDepositResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Shares.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) @@ -844,6 +920,39 @@ func (m *MsgWithdrawResponse) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: MsgWithdrawResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Shares.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/x/earn/types/vault.go b/x/earn/types/vault.go index c0841159..b3bde787 100644 --- a/x/earn/types/vault.go +++ b/x/earn/types/vault.go @@ -8,32 +8,15 @@ import ( ) // NewVaultRecord returns a new VaultRecord with 0 supply. -func NewVaultRecord(vaultDenom string) VaultRecord { +func NewVaultRecord(vaultDenom string, amount sdk.Dec) VaultRecord { return VaultRecord{ - Denom: vaultDenom, - TotalSupply: sdk.NewCoin(vaultDenom, sdk.ZeroInt()), + TotalShares: NewVaultShare(vaultDenom, amount), } } // Validate returns an error if a VaultRecord is invalid. func (vr *VaultRecord) Validate() error { - if err := sdk.ValidateDenom(vr.Denom); err != nil { - return sdkerrors.Wrap(ErrInvalidVaultDenom, err.Error()) - } - - if vr.TotalSupply.Denom != vr.Denom { - return fmt.Errorf( - "total supply denom %v does not match vault record denom %v", - vr.TotalSupply.Denom, - vr.Denom, - ) - } - - if err := vr.TotalSupply.Validate(); err != nil { - return fmt.Errorf("vault total supply is invalid: %w", err) - } - - return nil + return vr.TotalShares.Validate() } // VaultRecords is a slice of VaultRecord. @@ -48,11 +31,11 @@ func (vrs VaultRecords) Validate() error { return err } - if denoms[vr.Denom] { - return fmt.Errorf("duplicate vault denom %s", vr.Denom) + if denoms[vr.TotalShares.Denom] { + return fmt.Errorf("duplicate vault denom %s", vr.TotalShares.Denom) } - denoms[vr.Denom] = true + denoms[vr.TotalShares.Denom] = true } return nil @@ -60,10 +43,10 @@ func (vrs VaultRecords) Validate() error { // NewVaultShareRecord returns a new VaultShareRecord with the provided supplied // coins. -func NewVaultShareRecord(depositor sdk.AccAddress, supplied ...sdk.Coin) VaultShareRecord { +func NewVaultShareRecord(depositor sdk.AccAddress, shares VaultShares) VaultShareRecord { return VaultShareRecord{ - Depositor: depositor, - AmountSupplied: sdk.NewCoins(supplied...), + Depositor: depositor, + Shares: shares, } } @@ -73,8 +56,8 @@ func (vsr VaultShareRecord) Validate() error { return fmt.Errorf("depositor is empty") } - if err := vsr.AmountSupplied.Validate(); err != nil { - return fmt.Errorf("invalid vault share record amount supplied: %w", err) + if err := vsr.Shares.Validate(); err != nil { + return fmt.Errorf("invalid vault share record shares: %w", err) } return nil diff --git a/x/earn/types/vault.pb.go b/x/earn/types/vault.pb.go index 72c6d1bd..b1f21324 100644 --- a/x/earn/types/vault.pb.go +++ b/x/earn/types/vault.pb.go @@ -6,8 +6,8 @@ package types import ( fmt "fmt" _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types" github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" - types "github.com/cosmos/cosmos-sdk/types" _ "github.com/gogo/protobuf/gogoproto" proto "github.com/gogo/protobuf/proto" io "io" @@ -82,15 +82,10 @@ func (m *AllowedVault) GetVaultStrategy() StrategyType { return STRATEGY_TYPE_UNSPECIFIED } -// VaultRecord is the state of a vault and is used to store the state of a -// vault. +// VaultRecord is the state of a vault. type VaultRecord struct { - // Denom is the only supported denomination of the vault for deposits and - // withdrawals. - Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` - // TotalSupply is the total supply of the vault, denominated **only** in the - // user deposit/withdrawal denom, must be the same as the Denom field. - TotalSupply types.Coin `protobuf:"bytes,2,opt,name=total_supply,json=totalSupply,proto3" json:"total_supply"` + // TotalShares is the total distributed number of shares in the vault. + TotalShares VaultShare `protobuf:"bytes,1,opt,name=total_shares,json=totalShares,proto3" json:"total_shares"` } func (m *VaultRecord) Reset() { *m = VaultRecord{} } @@ -126,27 +121,19 @@ func (m *VaultRecord) XXX_DiscardUnknown() { var xxx_messageInfo_VaultRecord proto.InternalMessageInfo -func (m *VaultRecord) GetDenom() string { +func (m *VaultRecord) GetTotalShares() VaultShare { if m != nil { - return m.Denom + return m.TotalShares } - return "" -} - -func (m *VaultRecord) GetTotalSupply() types.Coin { - if m != nil { - return m.TotalSupply - } - return types.Coin{} + return VaultShare{} } // VaultShareRecord defines the vault shares owned by a depositor. type VaultShareRecord struct { // Depositor represents the owner of the shares Depositor github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,1,opt,name=depositor,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"depositor,omitempty"` - // AmountSupplied represents the total amount a depositor has supplied to the - // vault. The vault is determined by the coin denom. - AmountSupplied github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,2,rep,name=amount_supplied,json=amountSupplied,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"amount_supplied"` + // Shares represent the vault shares owned by the depositor. + Shares VaultShares `protobuf:"bytes,2,rep,name=shares,proto3,castrepeated=VaultShares" json:"shares"` } func (m *VaultShareRecord) Reset() { *m = VaultShareRecord{} } @@ -189,50 +176,97 @@ func (m *VaultShareRecord) GetDepositor() github_com_cosmos_cosmos_sdk_types.Acc return nil } -func (m *VaultShareRecord) GetAmountSupplied() github_com_cosmos_cosmos_sdk_types.Coins { +func (m *VaultShareRecord) GetShares() VaultShares { if m != nil { - return m.AmountSupplied + return m.Shares } return nil } +// VaultShare defines shares of a vault owned by a depositor. +type VaultShare struct { + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` + Amount github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=amount,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"amount"` +} + +func (m *VaultShare) Reset() { *m = VaultShare{} } +func (*VaultShare) ProtoMessage() {} +func (*VaultShare) Descriptor() ([]byte, []int) { + return fileDescriptor_884eb89509fbdc04, []int{3} +} +func (m *VaultShare) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *VaultShare) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_VaultShare.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 *VaultShare) XXX_Merge(src proto.Message) { + xxx_messageInfo_VaultShare.Merge(m, src) +} +func (m *VaultShare) XXX_Size() int { + return m.Size() +} +func (m *VaultShare) XXX_DiscardUnknown() { + xxx_messageInfo_VaultShare.DiscardUnknown(m) +} + +var xxx_messageInfo_VaultShare proto.InternalMessageInfo + +func (m *VaultShare) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + func init() { proto.RegisterType((*AllowedVault)(nil), "kava.earn.v1beta1.AllowedVault") proto.RegisterType((*VaultRecord)(nil), "kava.earn.v1beta1.VaultRecord") proto.RegisterType((*VaultShareRecord)(nil), "kava.earn.v1beta1.VaultShareRecord") + proto.RegisterType((*VaultShare)(nil), "kava.earn.v1beta1.VaultShare") } func init() { proto.RegisterFile("kava/earn/v1beta1/vault.proto", fileDescriptor_884eb89509fbdc04) } var fileDescriptor_884eb89509fbdc04 = []byte{ - // 417 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xbd, 0x8e, 0xd3, 0x40, - 0x10, 0xc7, 0xed, 0xe3, 0x43, 0xba, 0x4d, 0x08, 0x60, 0xae, 0xc8, 0x9d, 0x84, 0x6d, 0xa5, 0x40, - 0x69, 0xbc, 0xe6, 0x8e, 0x17, 0x20, 0x46, 0x42, 0xd4, 0x36, 0xa2, 0xa0, 0x89, 0xd6, 0xde, 0xc5, - 0xb1, 0x62, 0x7b, 0x2c, 0xef, 0x3a, 0xe0, 0xb7, 0xe0, 0x39, 0xa8, 0x79, 0x88, 0x94, 0x11, 0x15, - 0x55, 0x40, 0xc9, 0x0b, 0x50, 0x53, 0xa1, 0xfd, 0x48, 0x40, 0x8a, 0x40, 0x54, 0xde, 0x9d, 0x99, - 0xff, 0xfc, 0xfe, 0x33, 0x5e, 0xf4, 0x78, 0x49, 0x56, 0x24, 0x64, 0xa4, 0xad, 0xc3, 0xd5, 0x75, - 0xca, 0x04, 0xb9, 0x0e, 0x57, 0xa4, 0x2b, 0x05, 0x6e, 0x5a, 0x10, 0xe0, 0x3c, 0x94, 0x69, 0x2c, - 0xd3, 0xd8, 0xa4, 0xaf, 0x2e, 0x33, 0xe0, 0x15, 0xf0, 0xb9, 0x2a, 0x08, 0xf5, 0x45, 0x57, 0x5f, - 0xb9, 0xfa, 0x16, 0xa6, 0x84, 0xb3, 0x63, 0xbb, 0x0c, 0x8a, 0xda, 0xe4, 0x2f, 0x72, 0xc8, 0x41, - 0xeb, 0xe4, 0xc9, 0x44, 0xfd, 0x53, 0x0b, 0x5c, 0xb4, 0x44, 0xb0, 0xbc, 0xd7, 0x15, 0x93, 0x12, - 0x0d, 0x67, 0x65, 0x09, 0xef, 0x19, 0x7d, 0x23, 0xbd, 0x39, 0x17, 0xe8, 0x0e, 0x65, 0x35, 0x54, - 0x63, 0xdb, 0xb7, 0xa7, 0xe7, 0xb1, 0xbe, 0x38, 0x2f, 0xd1, 0x48, 0x59, 0x9f, 0x1f, 0xd4, 0xe3, - 0x33, 0xdf, 0x9e, 0x8e, 0x6e, 0x3c, 0x7c, 0x32, 0x04, 0x4e, 0x4c, 0xc9, 0xeb, 0xbe, 0x61, 0xf1, - 0x3d, 0x25, 0x3b, 0x84, 0x26, 0x39, 0x1a, 0x28, 0x4c, 0xcc, 0x32, 0x68, 0xe9, 0x5f, 0x60, 0x11, - 0x1a, 0x0a, 0x10, 0xa4, 0x9c, 0xf3, 0xae, 0x69, 0x4a, 0x8d, 0x1a, 0xdc, 0x5c, 0x62, 0xb3, 0x0f, - 0xb9, 0x81, 0x23, 0xec, 0x05, 0x14, 0x75, 0x74, 0x7b, 0xbd, 0xf5, 0xac, 0x78, 0xa0, 0x44, 0x89, - 0xd2, 0x4c, 0x7e, 0xd8, 0xe8, 0x81, 0x22, 0x25, 0x0b, 0xd2, 0x32, 0x83, 0x7b, 0x87, 0xce, 0x29, - 0x6b, 0x80, 0x17, 0x02, 0x5a, 0x85, 0x1c, 0x46, 0xaf, 0x7e, 0x6e, 0xbd, 0x20, 0x2f, 0xc4, 0xa2, - 0x4b, 0x71, 0x06, 0x95, 0xd9, 0xb9, 0xf9, 0x04, 0x9c, 0x2e, 0x43, 0xd1, 0x37, 0x8c, 0xe3, 0x59, - 0x96, 0xcd, 0x28, 0x6d, 0x19, 0xe7, 0x5f, 0x3e, 0x07, 0x8f, 0x8c, 0x13, 0x13, 0x89, 0x7a, 0xc1, - 0x78, 0xfc, 0xbb, 0xb5, 0x23, 0xd0, 0x7d, 0x52, 0x41, 0x57, 0x0b, 0x3d, 0x41, 0xc1, 0xe8, 0xf8, - 0xcc, 0xbf, 0xf5, 0xef, 0x19, 0x9e, 0xca, 0x19, 0x3e, 0x7d, 0xf3, 0xa6, 0xff, 0x61, 0x46, 0x0a, - 0x78, 0x3c, 0xd2, 0x8c, 0xc4, 0x20, 0xa2, 0xe7, 0xeb, 0x9d, 0x6b, 0x6f, 0x76, 0xae, 0xfd, 0x7d, - 0xe7, 0xda, 0x1f, 0xf7, 0xae, 0xb5, 0xd9, 0xbb, 0xd6, 0xd7, 0xbd, 0x6b, 0xbd, 0x7d, 0xf2, 0x47, - 0x4f, 0xf9, 0xbf, 0x82, 0x92, 0xa4, 0x5c, 0x9d, 0xc2, 0x0f, 0xfa, 0x71, 0xa8, 0xbe, 0xe9, 0x5d, - 0xf5, 0x24, 0x9e, 0xfd, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x80, 0xa4, 0xff, 0xb9, 0x02, 0x00, - 0x00, + // 445 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0x31, 0x8f, 0xd3, 0x30, + 0x18, 0x4d, 0x8e, 0xa3, 0x52, 0x9d, 0x72, 0x82, 0xdc, 0x0d, 0xc7, 0x49, 0x4d, 0xa2, 0x0c, 0xa8, + 0x4b, 0x12, 0xb5, 0x6c, 0x88, 0x81, 0x46, 0x55, 0x85, 0x18, 0xd3, 0xc2, 0xc0, 0x52, 0x39, 0x8e, + 0x49, 0xab, 0x26, 0x71, 0x14, 0xbb, 0x85, 0x2c, 0xfc, 0x06, 0x46, 0x46, 0x66, 0xe6, 0xfe, 0x06, + 0xd4, 0xb1, 0xea, 0x84, 0x18, 0x0a, 0x6a, 0xff, 0x05, 0x13, 0xb2, 0xe3, 0x12, 0xa4, 0x82, 0xb8, + 0x29, 0xf6, 0xfb, 0xde, 0x7b, 0xdf, 0xfb, 0xf2, 0x19, 0xb4, 0xe7, 0x70, 0x09, 0x3d, 0x0c, 0x8b, + 0xcc, 0x5b, 0x76, 0x43, 0xcc, 0x60, 0xd7, 0x5b, 0xc2, 0x45, 0xc2, 0xdc, 0xbc, 0x20, 0x8c, 0xe8, + 0x0f, 0x78, 0xd9, 0xe5, 0x65, 0x57, 0x96, 0x6f, 0x1e, 0x22, 0x42, 0x53, 0x42, 0x27, 0x82, 0xe0, + 0x55, 0x97, 0x8a, 0x7d, 0x63, 0x54, 0x37, 0x2f, 0x84, 0x14, 0xff, 0xb6, 0x43, 0x64, 0x96, 0xc9, + 0xfa, 0x55, 0x4c, 0x62, 0x52, 0xe9, 0xf8, 0x49, 0xa2, 0xd6, 0x69, 0x04, 0xca, 0x0a, 0xc8, 0x70, + 0x5c, 0x56, 0x0c, 0x3b, 0x01, 0xad, 0x7e, 0x92, 0x90, 0xb7, 0x38, 0x7a, 0xc5, 0xb3, 0xe9, 0x57, + 0xe0, 0x6e, 0x84, 0x33, 0x92, 0x5e, 0xab, 0x96, 0xda, 0x69, 0x06, 0xd5, 0x45, 0x1f, 0x82, 0x0b, + 0x11, 0x7d, 0x72, 0x54, 0x5f, 0x9f, 0x59, 0x6a, 0xe7, 0xa2, 0x67, 0xba, 0x27, 0x43, 0xb8, 0x23, + 0x49, 0x19, 0x97, 0x39, 0x0e, 0xee, 0x09, 0xd9, 0x11, 0xb2, 0x5f, 0x02, 0x4d, 0xb4, 0x09, 0x30, + 0x22, 0x45, 0xa4, 0x0f, 0x41, 0x8b, 0x11, 0x06, 0x93, 0x09, 0x9d, 0xc2, 0x02, 0x53, 0xd1, 0x53, + 0xeb, 0xb5, 0xff, 0x62, 0x2a, 0x54, 0x23, 0xce, 0xf2, 0xcf, 0xd7, 0x3b, 0x53, 0x09, 0x34, 0x21, + 0x14, 0x08, 0xb5, 0xbf, 0xa8, 0xe0, 0x7e, 0xcd, 0x90, 0xe6, 0x6f, 0x40, 0x33, 0xc2, 0x39, 0xa1, + 0x33, 0x46, 0x0a, 0xe1, 0xdc, 0xf2, 0x9f, 0xff, 0xdc, 0x99, 0x4e, 0x3c, 0x63, 0xd3, 0x45, 0xe8, + 0x22, 0x92, 0xca, 0x3f, 0x2c, 0x3f, 0x0e, 0x8d, 0xe6, 0x1e, 0x2b, 0x73, 0x4c, 0xdd, 0x3e, 0x42, + 0xfd, 0x28, 0x2a, 0x30, 0xa5, 0xdb, 0x95, 0x73, 0x29, 0xf7, 0x20, 0x11, 0xbf, 0x64, 0x98, 0x06, + 0xb5, 0xb5, 0xfe, 0x02, 0x34, 0x64, 0xfc, 0x33, 0xeb, 0xce, 0xff, 0xe3, 0x5f, 0xf2, 0xf8, 0x9f, + 0xbf, 0x9b, 0x5a, 0x8d, 0xd1, 0x40, 0x3a, 0xd8, 0xef, 0x01, 0xa8, 0xe1, 0x7f, 0xec, 0x62, 0x0c, + 0x1a, 0x30, 0x25, 0x8b, 0x8c, 0x89, 0x1d, 0x34, 0xfd, 0xa7, 0xdc, 0xf0, 0xdb, 0xce, 0x7c, 0x74, + 0x8b, 0xc1, 0x06, 0x18, 0x6d, 0x57, 0x0e, 0x90, 0x13, 0x0d, 0x30, 0x0a, 0xa4, 0xd7, 0x93, 0xf3, + 0x8f, 0x9f, 0x4c, 0xc5, 0x7f, 0xb6, 0xde, 0x1b, 0xea, 0x66, 0x6f, 0xa8, 0x3f, 0xf6, 0x86, 0xfa, + 0xe1, 0x60, 0x28, 0x9b, 0x83, 0xa1, 0x7c, 0x3d, 0x18, 0xca, 0xeb, 0x3f, 0xdd, 0xf9, 0x7c, 0x4e, + 0x02, 0x43, 0x2a, 0x4e, 0xde, 0xbb, 0xea, 0x81, 0x89, 0x0e, 0x61, 0x43, 0x3c, 0xab, 0xc7, 0xbf, + 0x02, 0x00, 0x00, 0xff, 0xff, 0x19, 0x75, 0x6f, 0x0e, 0xfd, 0x02, 0x00, 0x00, } func (m *AllowedVault) Marshal() (dAtA []byte, err error) { @@ -291,7 +325,7 @@ func (m *VaultRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { var l int _ = l { - size, err := m.TotalSupply.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.TotalShares.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -299,14 +333,7 @@ func (m *VaultRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { i = encodeVarintVault(dAtA, i, uint64(size)) } i-- - dAtA[i] = 0x12 - if len(m.Denom) > 0 { - i -= len(m.Denom) - copy(dAtA[i:], m.Denom) - i = encodeVarintVault(dAtA, i, uint64(len(m.Denom))) - i-- - dAtA[i] = 0xa - } + dAtA[i] = 0xa return len(dAtA) - i, nil } @@ -330,10 +357,10 @@ func (m *VaultShareRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if len(m.AmountSupplied) > 0 { - for iNdEx := len(m.AmountSupplied) - 1; iNdEx >= 0; iNdEx-- { + if len(m.Shares) > 0 { + for iNdEx := len(m.Shares) - 1; iNdEx >= 0; iNdEx-- { { - size, err := m.AmountSupplied[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + size, err := m.Shares[iNdEx].MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -354,6 +381,46 @@ func (m *VaultShareRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *VaultShare) 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 *VaultShare) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *VaultShare) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.Amount.Size() + i -= size + if _, err := m.Amount.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintVault(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintVault(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func encodeVarintVault(dAtA []byte, offset int, v uint64) int { offset -= sovVault(v) base := offset @@ -387,11 +454,7 @@ func (m *VaultRecord) Size() (n int) { } var l int _ = l - l = len(m.Denom) - if l > 0 { - n += 1 + l + sovVault(uint64(l)) - } - l = m.TotalSupply.Size() + l = m.TotalShares.Size() n += 1 + l + sovVault(uint64(l)) return n } @@ -406,8 +469,8 @@ func (m *VaultShareRecord) Size() (n int) { if l > 0 { n += 1 + l + sovVault(uint64(l)) } - if len(m.AmountSupplied) > 0 { - for _, e := range m.AmountSupplied { + if len(m.Shares) > 0 { + for _, e := range m.Shares { l = e.Size() n += 1 + l + sovVault(uint64(l)) } @@ -415,6 +478,21 @@ func (m *VaultShareRecord) Size() (n int) { return n } +func (m *VaultShare) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovVault(uint64(l)) + } + l = m.Amount.Size() + n += 1 + l + sovVault(uint64(l)) + return n +} + func sovVault(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -553,39 +631,7 @@ func (m *VaultRecord) Unmarshal(dAtA []byte) error { 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 ErrIntOverflowVault - } - 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 ErrInvalidLengthVault - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthVault - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Denom = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TotalSupply", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field TotalShares", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -612,7 +658,7 @@ func (m *VaultRecord) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.TotalSupply.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.TotalShares.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -702,7 +748,7 @@ func (m *VaultShareRecord) Unmarshal(dAtA []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AmountSupplied", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Shares", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -729,8 +775,124 @@ func (m *VaultShareRecord) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AmountSupplied = append(m.AmountSupplied, types.Coin{}) - if err := m.AmountSupplied[len(m.AmountSupplied)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.Shares = append(m.Shares, VaultShare{}) + if err := m.Shares[len(m.Shares)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipVault(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthVault + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *VaultShare) 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 ErrIntOverflowVault + } + 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: VaultShare: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: VaultShare: 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 ErrIntOverflowVault + } + 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 ErrInvalidLengthVault + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthVault + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Amount", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowVault + } + 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 ErrInvalidLengthVault + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthVault + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Amount.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/earn/types/vault_test.go b/x/earn/types/vault_test.go index 99618375..3eec396a 100644 --- a/x/earn/types/vault_test.go +++ b/x/earn/types/vault_test.go @@ -25,12 +25,10 @@ func TestVaultRecordValidate(t *testing.T) { name: "valid vault records", vaultRecords: types.VaultRecords{ { - Denom: "usdx", - TotalSupply: sdk.NewInt64Coin("usdx", 0), + TotalShares: types.NewVaultShare("usdx", sdk.NewDec(0)), }, { - Denom: "ukava", - TotalSupply: sdk.NewInt64Coin("ukava", 5), + TotalShares: types.NewVaultShare("ukava", sdk.NewDec(5)), }, }, errArgs: errArgs{ @@ -41,12 +39,10 @@ func TestVaultRecordValidate(t *testing.T) { name: "invalid - duplicate denom", vaultRecords: types.VaultRecords{ { - Denom: "usdx", - TotalSupply: sdk.NewInt64Coin("usdx", 0), + TotalShares: types.NewVaultShare("usdx", sdk.NewDec(0)), }, { - Denom: "usdx", - TotalSupply: sdk.NewInt64Coin("usdx", 5), + TotalShares: types.NewVaultShare("usdx", sdk.NewDec(5)), }, }, errArgs: errArgs{ @@ -58,8 +54,7 @@ func TestVaultRecordValidate(t *testing.T) { name: "invalid - invalid denom", vaultRecords: types.VaultRecords{ { - Denom: "", - TotalSupply: sdk.NewInt64Coin("ukava", 0), + TotalShares: types.VaultShare{Denom: "", Amount: sdk.NewDec(0)}, }, }, errArgs: errArgs{ @@ -67,30 +62,16 @@ func TestVaultRecordValidate(t *testing.T) { contains: "invalid denom", }, }, - { - name: "invalid - mismatch denom", - vaultRecords: types.VaultRecords{ - { - Denom: "usdx", - TotalSupply: sdk.NewInt64Coin("ukava", 0), - }, - }, - errArgs: errArgs{ - expectPass: false, - contains: "total supply denom ukava does not match vault record denom usdx", - }, - }, { name: "invalid - negative", vaultRecords: types.VaultRecords{ { - Denom: "usdx", - TotalSupply: sdk.Coin{Denom: "usdx", Amount: sdk.NewInt(-1)}, + TotalShares: types.VaultShare{"usdx", sdk.NewDec(-5)}, }, }, errArgs: errArgs{ expectPass: false, - contains: "negative coin amount", + contains: "vault share amount -5.000000000000000000 is negative", }, }, } @@ -127,15 +108,15 @@ func TestVaultShareRecordsValidate(t *testing.T) { vaultRecords: types.VaultShareRecords{ { Depositor: addrs[0], - AmountSupplied: sdk.NewCoins( - sdk.NewInt64Coin("usdx", 0), + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(0)), ), }, { Depositor: addrs[1], - AmountSupplied: sdk.NewCoins( - sdk.NewInt64Coin("usdx", 0), - sdk.NewInt64Coin("ukava", 5), + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(0)), + types.NewVaultShare("ukava", sdk.NewDec(5)), ), }, }, @@ -148,15 +129,15 @@ func TestVaultShareRecordsValidate(t *testing.T) { vaultRecords: types.VaultShareRecords{ { Depositor: addrs[0], - AmountSupplied: sdk.NewCoins( - sdk.NewInt64Coin("usdx", 0), + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(0)), ), }, { Depositor: addrs[0], - AmountSupplied: sdk.NewCoins( - sdk.NewInt64Coin("usdx", 0), - sdk.NewInt64Coin("ukava", 5), + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(0)), + types.NewVaultShare("ukava", sdk.NewDec(5)), ), }, }, @@ -170,8 +151,8 @@ func TestVaultShareRecordsValidate(t *testing.T) { vaultRecords: types.VaultShareRecords{ { Depositor: sdk.AccAddress{}, - AmountSupplied: sdk.NewCoins( - sdk.NewInt64Coin("usdx", 0), + Shares: types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(0)), ), }, }, @@ -185,14 +166,15 @@ func TestVaultShareRecordsValidate(t *testing.T) { vaultRecords: types.VaultShareRecords{ { Depositor: addrs[0], - AmountSupplied: sdk.Coins{ - sdk.Coin{Denom: "ukava", Amount: sdk.NewInt(-1)}, + // Direct slice, not NewVaultShares() which panics + Shares: types.VaultShares{ + types.VaultShare{"usdx", sdk.NewDec(-5)}, }, }, }, errArgs: errArgs{ expectPass: false, - contains: "amount is not positive", + contains: "invalid vault share record shares: share -5.000000000000000000usdx amount is not positive", }, }, } @@ -300,11 +282,11 @@ func TestAllowedVaultsValidate(t *testing.T) { func TestNewVaultShareRecord(t *testing.T) { _, addrs := app.GeneratePrivKeyAddressPairs(1) - coins := sdk.NewCoins( - sdk.NewInt64Coin("usdx", 10), - sdk.NewInt64Coin("ukava", 5), + shares := types.NewVaultShares( + types.NewVaultShare("usdx", sdk.NewDec(0)), + types.NewVaultShare("ukava", sdk.NewDec(5)), ) - shareRecord := types.NewVaultShareRecord(addrs[0], coins...) - require.Equal(t, coins, shareRecord.AmountSupplied) + shareRecord := types.NewVaultShareRecord(addrs[0], shares) + require.Equal(t, shares, shareRecord.Shares) }