From 88d4868316f2bddf07fa422111db6614ddb8b59b Mon Sep 17 00:00:00 2001 From: Derrick Lee Date: Thu, 28 Jul 2022 09:39:57 -0700 Subject: [PATCH] Implement Hard strategy for Earn vaults (#1278) * Simplify strategies to lend and savings * Add hard and savings keepers * Add ctx to strategy interface, fill in lend strategy * Rename lend strategy to hard * Fix hard deposit query, fix withdraw bank send * Fix misleading borrow instead of withdraw for hard * Remove liquidateall strategy method * Withdraw tests * Add hard gs to testutil suite * Update withdraw tests with working hard strategy, clean strategy interface methods * Check allowed denom for strategy * Update GetVaultTotalValue doc note * Update error wrap message for unsupported denom * Remove unnecessary viewvault keeper * Withdraw amount from account value, not supplied value * Test value > supplied withdraw * Use dec when dividing for withdrawAmountPercent * Use the correct store prefix for vault shares * Update swap references to earn * Simplify vault shares, use a single share for all coins per address --- app/app.go | 8 +- docs/core/proto-docs.md | 13 +- proto/kava/earn/v1beta1/genesis.proto | 2 +- proto/kava/earn/v1beta1/strategy.proto | 8 +- proto/kava/earn/v1beta1/vault.proto | 9 +- x/earn/client/cli/tx.go | 6 +- x/earn/keeper/deposit.go | 29 ++- x/earn/keeper/deposit_test.go | 20 +- x/earn/keeper/grpc_query_test.go | 6 +- x/earn/keeper/keeper.go | 8 + x/earn/keeper/msg_server_test.go | 4 +- x/earn/keeper/strategy.go | 33 +-- x/earn/keeper/strategy_hard.go | 48 ++++ x/earn/keeper/strategy_hard_test.go | 300 +++++++++++++++++++++++++ x/earn/keeper/strategy_stablecoin.go | 42 ---- x/earn/keeper/vault.go | 64 ++---- x/earn/keeper/vault_test.go | 46 ++-- x/earn/keeper/withdraw.go | 104 ++++++--- x/earn/keeper/withdraw_test.go | 38 ++-- x/earn/testutil/suite.go | 158 ++++++++++++- x/earn/types/errors.go | 13 +- x/earn/types/events.go | 2 +- x/earn/types/expected_keepers.go | 19 ++ x/earn/types/genesis.pb.go | 2 +- x/earn/types/keys.go | 19 +- x/earn/types/strategy.pb.go | 44 ++-- x/earn/types/vault.go | 7 +- x/earn/types/vault.pb.go | 97 ++++---- 28 files changed, 813 insertions(+), 336 deletions(-) create mode 100644 x/earn/keeper/strategy_hard.go create mode 100644 x/earn/keeper/strategy_hard_test.go delete mode 100644 x/earn/keeper/strategy_stablecoin.go diff --git a/app/app.go b/app/app.go index bfb3d202..48d22168 100644 --- a/app/app.go +++ b/app/app.go @@ -624,6 +624,8 @@ func NewApp( earnSubspace, app.accountKeeper, app.bankKeeper, + hardKeeper, + savingsKeeper, ) // create committee keeper with router @@ -996,9 +998,11 @@ func (app *App) RegisterTendermintService(clientCtx client.Context) { func (app *App) loadBlockedMaccAddrs() map[string]bool { modAccAddrs := app.ModuleAccountAddrs() kavadistMaccAddr := app.accountKeeper.GetModuleAddress(kavadisttypes.ModuleName) + earnMaccAddr := app.accountKeeper.GetModuleAddress(earntypes.ModuleName) + for addr := range modAccAddrs { - // Set the kavadist module account address as unblocked - if addr == kavadistMaccAddr.String() { + // Set the kavadist and earn module account address as unblocked + if addr == kavadistMaccAddr.String() || addr == earnMaccAddr.String() { modAccAddrs[addr] = false } } diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 931be305..e6fba347 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -2752,9 +2752,8 @@ Msg defines the committee Msg service | Name | Number | Description | | ---- | ------ | ----------- | | STRATEGY_TYPE_UNKNOWN | 0 | | -| STRATEGY_TYPE_KAVA_STAKERS | 1 | | -| STRATEGY_TYPE_STABLECOIN_STAKERS | 2 | USDC / BUSD vaults use the same strategy but with the denom set in VaultRecord | -| STRATEGY_TYPE_KAVA_FOUNDATION | 3 | | +| STRATEGY_TYPE_HARD | 1 | | +| STRATEGY_TYPE_SAVINGS | 2 | | @@ -2809,13 +2808,13 @@ vault. ### VaultShareRecord -VaultShareRecord defines the shares owned by a depositor and vault. +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) | | amount_supplied represents the total amount a depositor has supplied to the vault. The vault is determined by the coin denom. | +| `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. | @@ -2872,7 +2871,7 @@ VaultShareRecord defines the shares owned by a depositor and vault. ### GenesisState -GenesisState defines the swap module's genesis state. +GenesisState defines the earn module's genesis state. | Field | Type | Label | Description | diff --git a/proto/kava/earn/v1beta1/genesis.proto b/proto/kava/earn/v1beta1/genesis.proto index cb6c889e..6bf239f2 100644 --- a/proto/kava/earn/v1beta1/genesis.proto +++ b/proto/kava/earn/v1beta1/genesis.proto @@ -7,7 +7,7 @@ import "kava/earn/v1beta1/vault.proto"; import "kava/earn/v1beta1/params.proto"; import "gogoproto/gogo.proto"; -// GenesisState defines the swap module's genesis state. +// GenesisState defines the earn module's genesis state. message GenesisState { // params defines all the paramaters related to earn Params params = 1 [(gogoproto.nullable) = false]; diff --git a/proto/kava/earn/v1beta1/strategy.proto b/proto/kava/earn/v1beta1/strategy.proto index 4af69980..7b3d6f24 100644 --- a/proto/kava/earn/v1beta1/strategy.proto +++ b/proto/kava/earn/v1beta1/strategy.proto @@ -10,9 +10,7 @@ import "cosmos_proto/cosmos.proto"; enum StrategyType { option (gogoproto.goproto_enum_prefix) = false; - STRATEGY_TYPE_UNKNOWN = 0; - STRATEGY_TYPE_KAVA_STAKERS = 1; - // USDC / BUSD vaults use the same strategy but with the denom set in VaultRecord - STRATEGY_TYPE_STABLECOIN_STAKERS = 2; - STRATEGY_TYPE_KAVA_FOUNDATION = 3; + STRATEGY_TYPE_UNKNOWN = 0; + STRATEGY_TYPE_HARD = 1; + STRATEGY_TYPE_SAVINGS = 2; } diff --git a/proto/kava/earn/v1beta1/vault.proto b/proto/kava/earn/v1beta1/vault.proto index b133b41d..3467ad25 100644 --- a/proto/kava/earn/v1beta1/vault.proto +++ b/proto/kava/earn/v1beta1/vault.proto @@ -29,14 +29,15 @@ message VaultRecord { cosmos.base.v1beta1.Coin total_supply = 2 [(gogoproto.nullable) = false]; } -// VaultShareRecord defines the shares owned by a depositor and vault. +// VaultShareRecord defines the vault shares owned by a depositor. message VaultShareRecord { - // depositor represents the owner of the shares + // Depositor represents the owner of the shares bytes depositor = 1 [ (cosmos_proto.scalar) = "cosmos.AddressBytes", (gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress" ]; - // amount_supplied represents the total amount a depositor has supplied to the + // AmountSupplied represents the total amount a depositor has supplied to the // vault. The vault is determined by the coin denom. - cosmos.base.v1beta1.Coin amount_supplied = 2 [(gogoproto.nullable) = false]; + 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 diff --git a/x/earn/client/cli/tx.go b/x/earn/client/cli/tx.go index 0591782c..af42a25d 100644 --- a/x/earn/client/cli/tx.go +++ b/x/earn/client/cli/tx.go @@ -16,7 +16,7 @@ import ( // GetTxCmd returns the transaction commands for this module func GetTxCmd() *cobra.Command { - swapTxCmd := &cobra.Command{ + earnTxCmd := &cobra.Command{ Use: types.ModuleName, Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), DisableFlagParsing: true, @@ -33,9 +33,9 @@ func GetTxCmd() *cobra.Command { flags.AddTxFlagsToCmd(cmd) } - swapTxCmd.AddCommand(cmds...) + earnTxCmd.AddCommand(cmds...) - return swapTxCmd + return earnTxCmd } func getCmdDeposit() *cobra.Command { diff --git a/x/earn/keeper/deposit.go b/x/earn/keeper/deposit.go index e0bc6209..d9525320 100644 --- a/x/earn/keeper/deposit.go +++ b/x/earn/keeper/deposit.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/earn/types" ) @@ -25,6 +26,22 @@ func (k *Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.C vaultRecord = types.NewVaultRecord(amount.Denom) } + // Get the strategy for the vault + strategy, err := k.GetStrategy(allowedVault.VaultStrategy) + if err != nil { + return err + } + + // Check if this denom is allowed for the strategy + if !strategy.IsDenomSupported(amount.Denom) { + return sdkerrors.Wrapf( + types.ErrStrategyDenomNotSupported, + "denom %s is not supported by the strategy %s", + amount.Denom, + strategy.GetStrategyType(), + ) + } + // Transfer amount to module account if err := k.bankKeeper.SendCoinsFromAccountToModule( ctx, @@ -36,10 +53,10 @@ func (k *Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.C } // Get VaultShareRecord for account, create if not exist - vaultShareRecord, found := k.GetVaultShareRecord(ctx, amount.Denom, depositor) + vaultShareRecord, found := k.GetVaultShareRecord(ctx, depositor) if !found { // Create a new empty VaultShareRecord with 0 supply - vaultShareRecord = types.NewVaultShareRecord(depositor, amount.Denom) + vaultShareRecord = types.NewVaultShareRecord(depositor) } // Increment VaultRecord supply @@ -52,14 +69,8 @@ func (k *Keeper) Deposit(ctx sdk.Context, depositor sdk.AccAddress, amount sdk.C k.SetVaultRecord(ctx, vaultRecord) k.SetVaultShareRecord(ctx, vaultShareRecord) - // Get the strategy for the vault - strategy, err := k.GetStrategy(allowedVault.VaultStrategy) - if err != nil { - return err - } - // Deposit to the strategy - if err := strategy.Deposit(amount); err != nil { + if err := strategy.Deposit(ctx, amount); err != nil { return err } diff --git a/x/earn/keeper/deposit_test.go b/x/earn/keeper/deposit_test.go index 1499edde..0f5bdc27 100644 --- a/x/earn/keeper/deposit_test.go +++ b/x/earn/keeper/deposit_test.go @@ -34,11 +34,11 @@ func TestDepositTestSuite(t *testing.T) { } func (suite *depositTestSuite) TestDeposit_Balances() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -50,18 +50,16 @@ func (suite *depositTestSuite) TestDeposit_Balances() { sdk.NewCoins(startBalance.Sub(depositAmount)), // Account decreases by deposit ) - // TODO: Module account balance will be zero when strategies are implemented - suite.ModuleAccountBalanceEqual( - sdk.NewCoins(depositAmount), - ) + suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalSuppliedEqual(sdk.NewCoins(depositAmount)) } func (suite *depositTestSuite) TestDeposit_Exceed() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 1001) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -82,11 +80,11 @@ func (suite *depositTestSuite) TestDeposit_Exceed() { } func (suite *depositTestSuite) TestDeposit_Zero() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 0) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -107,7 +105,7 @@ func (suite *depositTestSuite) TestDeposit_Zero() { } func (suite *depositTestSuite) TestDeposit_InvalidVault() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 1001) diff --git a/x/earn/keeper/grpc_query_test.go b/x/earn/keeper/grpc_query_test.go index d0789bc8..99eca6d1 100644 --- a/x/earn/keeper/grpc_query_test.go +++ b/x/earn/keeper/grpc_query_test.go @@ -33,7 +33,7 @@ func TestGrpcQueryTestSuite(t *testing.T) { } func (suite *grpcQueryTestSuite) TestQueryParams() { - vaultDenom := "busd" + vaultDenom := "usdx" res, err := suite.queryClient.Params(context.Background(), types.NewQueryParamsRequest()) suite.Require().NoError(err) @@ -41,14 +41,14 @@ func (suite *grpcQueryTestSuite) TestQueryParams() { suite.Require().ElementsMatch(types.DefaultParams().AllowedVaults, res.Params.AllowedVaults) // Add vault to params - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) // Query again for added vault res, err = suite.queryClient.Params(context.Background(), types.NewQueryParamsRequest()) suite.Require().NoError(err) suite.Require().Equal( types.AllowedVaults{ - types.NewAllowedVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS), + types.NewAllowedVault(vaultDenom, types.STRATEGY_TYPE_HARD), }, res.Params.AllowedVaults, ) diff --git a/x/earn/keeper/keeper.go b/x/earn/keeper/keeper.go index 5fe1ae23..bce868a4 100644 --- a/x/earn/keeper/keeper.go +++ b/x/earn/keeper/keeper.go @@ -15,6 +15,10 @@ type Keeper struct { paramSubspace paramtypes.Subspace accountKeeper types.AccountKeeper bankKeeper types.BankKeeper + + // Keepers used for strategies + hardKeeper types.HardKeeper + savingsKeeper types.SavingsKeeper } // NewKeeper creates a new keeper @@ -24,6 +28,8 @@ func NewKeeper( paramstore paramtypes.Subspace, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper, + hardKeeper types.HardKeeper, + savingsKeeper types.SavingsKeeper, ) Keeper { if !paramstore.HasKeyTable() { paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) @@ -35,5 +41,7 @@ func NewKeeper( paramSubspace: paramstore, accountKeeper: accountKeeper, bankKeeper: bankKeeper, + hardKeeper: hardKeeper, + savingsKeeper: savingsKeeper, } } diff --git a/x/earn/keeper/msg_server_test.go b/x/earn/keeper/msg_server_test.go index 57e62bd2..0a4ddaaf 100644 --- a/x/earn/keeper/msg_server_test.go +++ b/x/earn/keeper/msg_server_test.go @@ -34,7 +34,7 @@ func TestMsgServerTestSuite(t *testing.T) { func (suite *msgServerTestSuite) TestDeposit() { vaultDenom := "usdx" - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) @@ -85,7 +85,7 @@ func (suite *msgServerTestSuite) TestDeposit() { func (suite *msgServerTestSuite) TestWithdraw() { vaultDenom := "usdx" - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) diff --git a/x/earn/keeper/strategy.go b/x/earn/keeper/strategy.go index de8c0365..dd5ceb58 100644 --- a/x/earn/keeper/strategy.go +++ b/x/earn/keeper/strategy.go @@ -9,15 +9,12 @@ import ( // Strategy is the interface that must be implemented by a strategy. type Strategy interface { - // GetName returns the name of the strategy. - GetName() string + // GetStrategyType returns the strategy type + GetStrategyType() types.StrategyType - // GetDescription returns the description of the strategy. - GetDescription() string - - // GetSupportedDenoms returns a slice of supported denom for this strategy. - // For example, stablecoin stakers strategy supports both "busd" and "usdc". - GetSupportedDenoms() []string + // IsDenomSupported returns true if the denom is supported for this + // strategy. For example, the hard strategy supports "usdx". + IsDenomSupported(string) bool // GetEstimatedTotalAssets returns the estimated total assets denominated in // GetDenom() of this strategy. This is the value if the strategy were to @@ -25,29 +22,23 @@ type Strategy interface { // // **Note:** This may not reflect the true value as it may become outdated // from market changes. - GetEstimatedTotalAssets(denom string) (sdk.Coin, error) + GetEstimatedTotalAssets(ctx sdk.Context, denom string) (sdk.Coin, error) // Deposit the specified amount of coins into this strategy. The amount // must be denominated in GetDenom(). - Deposit(amount sdk.Coin) error + Deposit(ctx sdk.Context, amount sdk.Coin) error // Withdraw the specified amount of coins from this strategy. The amount // must be denominated in GetDenom(). - Withdraw(amount sdk.Coin) error - - // LiquidateAll liquidates all of the entire strategy's positions, returning - // the amount of liquidated denominated in GetDenom(). This should be only - // called during use of emergency via governance. - LiquidateAll() (amount sdk.Coin, err error) + Withdraw(ctx sdk.Context, amount sdk.Coin) error } +// GetStrategy returns the strategy for the given strategy type. func (k *Keeper) GetStrategy(strategyType types.StrategyType) (Strategy, error) { switch strategyType { - case types.STRATEGY_TYPE_STABLECOIN_STAKERS: - return (*StableCoinStrategy)(k), nil - case types.STRATEGY_TYPE_KAVA_STAKERS: - panic("unimplemented") - case types.STRATEGY_TYPE_KAVA_FOUNDATION: + case types.STRATEGY_TYPE_HARD: + return (*HardStrategy)(k), nil + case types.STRATEGY_TYPE_SAVINGS: panic("unimplemented") default: return nil, fmt.Errorf("unknown strategy type: %s", strategyType) diff --git a/x/earn/keeper/strategy_hard.go b/x/earn/keeper/strategy_hard.go new file mode 100644 index 00000000..e2a86869 --- /dev/null +++ b/x/earn/keeper/strategy_hard.go @@ -0,0 +1,48 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/earn/types" +) + +// HardStrategy defines the strategy that deposits assets to Hard +type HardStrategy Keeper + +var _ Strategy = (*HardStrategy)(nil) + +func (s *HardStrategy) GetStrategyType() types.StrategyType { + return types.STRATEGY_TYPE_HARD +} + +func (s *HardStrategy) IsDenomSupported(denom string) bool { + return denom == "usdx" +} + +func (s *HardStrategy) GetEstimatedTotalAssets(ctx sdk.Context, denom string) (sdk.Coin, error) { + macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName) + deposit, found := s.hardKeeper.GetSyncedDeposit(ctx, macc.GetAddress()) + if !found { + // Return 0 if no deposit exists for module account + return sdk.NewCoin(denom, sdk.ZeroInt()), nil + } + + // Only return the deposit for the vault denom. + for _, coin := range deposit.Amount { + if coin.Denom == denom { + return coin, nil + } + } + + // Return 0 if no deposit exists for the vault denom + return sdk.NewCoin(denom, sdk.ZeroInt()), nil +} + +func (s *HardStrategy) Deposit(ctx sdk.Context, amount sdk.Coin) error { + macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName) + return s.hardKeeper.Deposit(ctx, macc.GetAddress(), sdk.NewCoins(amount)) +} + +func (s *HardStrategy) Withdraw(ctx sdk.Context, amount sdk.Coin) error { + macc := s.accountKeeper.GetModuleAccount(ctx, types.ModuleName) + return s.hardKeeper.Withdraw(ctx, macc.GetAddress(), sdk.NewCoins(amount)) +} diff --git a/x/earn/keeper/strategy_hard_test.go b/x/earn/keeper/strategy_hard_test.go new file mode 100644 index 00000000..33a172bd --- /dev/null +++ b/x/earn/keeper/strategy_hard_test.go @@ -0,0 +1,300 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/earn/testutil" + "github.com/kava-labs/kava/x/earn/types" + + "github.com/stretchr/testify/suite" +) + +type strategyHardTestSuite struct { + testutil.Suite +} + +func (suite *strategyHardTestSuite) SetupTest() { + suite.Suite.SetupTest() + suite.Keeper.SetParams(suite.Ctx, types.DefaultParams()) +} + +func TestStrategyLendTestSuite(t *testing.T) { + suite.Run(t, new(strategyHardTestSuite)) +} + +func (suite *strategyHardTestSuite) TestGetSupportedDenoms() { + strategy, err := suite.Keeper.GetStrategy(types.STRATEGY_TYPE_HARD) + suite.Require().NoError(err) + + suite.True(strategy.IsDenomSupported("usdx")) +} + +func (suite *strategyHardTestSuite) TestGetStrategyType() { + strategy, err := suite.Keeper.GetStrategy(types.STRATEGY_TYPE_HARD) + suite.Require().NoError(err) + + suite.Equal(types.STRATEGY_TYPE_HARD, strategy.GetStrategyType()) +} + +func (suite *strategyHardTestSuite) TestDeposit_InvalidDenom() { + // Not supported by hard strategy + vaultDenom := "busd" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().Error(err) + suite.Require().ErrorIs( + err, + types.ErrStrategyDenomNotSupported, + "strategy should only allow usdx deposits", + ) +} + +func (suite *strategyHardTestSuite) TestDeposit_SingleAcc() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().NoError(err) + + suite.HardDepositAmountEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalSuppliedEqual(sdk.NewCoins(depositAmount)) + + // Query vault total + totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + + suite.Equal(depositAmount, totalValue) +} + +func (suite *strategyHardTestSuite) TestDeposit_SingleAcc_MultipleDeposits() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().NoError(err) + + // Second deposit + 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) + + // Query vault total + totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + + suite.Equal(depositAmount.Add(depositAmount), totalValue) +} + +func (suite *strategyHardTestSuite) TestDeposit_MultipleAcc_MultipleDeposits() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + expectedTotalValue := sdk.NewCoin(vaultDenom, depositAmount.Amount.MulRaw(4)) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + // 2 deposits each account + for i := 0; i < 2; i++ { + // Deposit from acc1 + err := suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), depositAmount) + suite.Require().NoError(err) + + // Deposit from acc2 + err = suite.Keeper.Deposit(suite.Ctx, acc2.GetAddress(), depositAmount) + suite.Require().NoError(err) + } + + suite.HardDepositAmountEqual(sdk.NewCoins(expectedTotalValue)) + suite.VaultTotalValuesEqual(sdk.NewCoins(expectedTotalValue)) + suite.VaultTotalSuppliedEqual(sdk.NewCoins(expectedTotalValue)) + + // Query vault total + totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + + suite.Equal(expectedTotalValue, totalValue) +} + +func (suite *strategyHardTestSuite) TestGetVaultTotalValue_Empty() { + vaultDenom := "usdx" + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // Query vault total + totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + + suite.Equal(sdk.NewCoin(vaultDenom, sdk.ZeroInt()), totalValue) +} + +func (suite *strategyHardTestSuite) TestGetVaultTotalValue_NoDenomDeposit() { + // 2 Vaults usdx, busd + // 1st vault has deposits + // 2nd vault has no deposits + vaultDenom := "usdx" + vaultDenomBusd := "busd" + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + suite.CreateVault(vaultDenomBusd, types.STRATEGY_TYPE_HARD) + + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + + // Deposit vault1 + err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().NoError(err) + + // Query vault total, hard deposit exists for account, but amount in busd does not + // Vault2 does not have any value, only returns amount for the correct denom + // if a hard deposit already exists + totalValueBusd, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenomBusd) + suite.Require().NoError(err) + + suite.Equal(sdk.NewCoin(vaultDenomBusd, sdk.ZeroInt()), totalValueBusd) +} + +// ---------------------------------------------------------------------------- +// Withdraw + +func (suite *strategyHardTestSuite) TestWithdraw() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) + err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().NoError(err) + + suite.HardDepositAmountEqual(sdk.NewCoins(depositAmount)) + + // Query vault total + totalValue, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + suite.Equal(depositAmount, totalValue) + + // Withdraw + err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().NoError(err) + + suite.HardDepositAmountEqual(sdk.NewCoins()) + suite.VaultTotalValuesEqual(sdk.NewCoins()) + suite.VaultTotalSuppliedEqual(sdk.NewCoins()) + + totalValue, err = suite.Keeper.GetVaultTotalValue(suite.Ctx, vaultDenom) + suite.Require().NoError(err) + suite.Equal(sdk.NewInt64Coin(vaultDenom, 0), totalValue) + + // Withdraw again + err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), depositAmount) + suite.Require().Error(err) + suite.Require().ErrorIs(err, types.ErrVaultRecordNotFound, "vault should be deleted when no more supply") +} + +func (suite *strategyHardTestSuite) TestWithdraw_OnlyWithdrawOwnSupply() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // Deposits from 2 accounts + acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress() + acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1).GetAddress() + err := suite.Keeper.Deposit(suite.Ctx, acc1, depositAmount) + suite.Require().NoError(err) + + err = suite.Keeper.Deposit(suite.Ctx, acc2, depositAmount) + suite.Require().NoError(err) + + // Withdraw + err = suite.Keeper.Withdraw(suite.Ctx, acc1, depositAmount) + suite.Require().NoError(err) + + // Withdraw again + err = suite.Keeper.Withdraw(suite.Ctx, acc1, depositAmount) + suite.Require().Error(err) + suite.Require().ErrorIs( + err, + types.ErrVaultShareRecordNotFound, + "should only be able to withdraw the account's own supply", + ) +} + +func (suite *strategyHardTestSuite) TestWithdraw_WithAccumulatedHard() { + vaultDenom := "usdx" + startBalance := sdk.NewInt64Coin(vaultDenom, 1000) + depositAmount := sdk.NewInt64Coin(vaultDenom, 100) + + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) + + // Deposits from 2 accounts + acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0).GetAddress() + err := suite.Keeper.Deposit(suite.Ctx, acc, 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))) + macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + suite.HardKeeper.Deposit(suite.Ctx, macc.GetAddress(), sdk.NewCoins(sdk.NewInt64Coin(vaultDenom, 10))) + + // 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 + err = suite.Keeper.Withdraw(suite.Ctx, acc, depositAmount) + suite.Require().NoError(err) + + // Withdraw again -- too much + err = suite.Keeper.Withdraw(suite.Ctx, acc, depositAmount) + suite.Require().Error(err) + suite.Require().ErrorIs( + err, + types.ErrInsufficientValue, + "cannot withdraw more than account value", + ) + + // Half of remaining 10, 5 remaining + err = suite.Keeper.Withdraw(suite.Ctx, acc, sdk.NewCoin(vaultDenom, sdk.NewInt(5))) + suite.Require().NoError(err) + + // Withdraw all + 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) +} diff --git a/x/earn/keeper/strategy_stablecoin.go b/x/earn/keeper/strategy_stablecoin.go deleted file mode 100644 index f1c3ea7c..00000000 --- a/x/earn/keeper/strategy_stablecoin.go +++ /dev/null @@ -1,42 +0,0 @@ -package keeper - -import sdk "github.com/cosmos/cosmos-sdk/types" - -// StableCoinStrategy defines the stablecoin strategy: -// 1. Supply USDX to Lend -type StableCoinStrategy Keeper - -var _ Strategy = (*StableCoinStrategy)(nil) - -func (s *StableCoinStrategy) GetName() string { - return "USDX" -} - -func (s *StableCoinStrategy) GetDescription() string { - return "Supplies the USDX to Lend" -} - -func (s *StableCoinStrategy) GetSupportedDenoms() []string { - return []string{"usdx"} -} - -func (s *StableCoinStrategy) GetEstimatedTotalAssets(denom string) (sdk.Coin, error) { - // 1. Get amount of USDX in Lend - - return sdk.Coin{}, nil -} - -func (s *StableCoinStrategy) Deposit(amount sdk.Coin) error { - return nil -} - -func (s *StableCoinStrategy) Withdraw(amount sdk.Coin) error { - return nil -} - -// LiquidateAll liquidates all assets in the strategy, this should be called -// only in case of emergency or when all assets should be moved to a new -// strategy. -func (s *StableCoinStrategy) LiquidateAll() (amount sdk.Coin, err error) { - return sdk.Coin{}, nil -} diff --git a/x/earn/keeper/vault.go b/x/earn/keeper/vault.go index fb4f1504..a6adcf2c 100644 --- a/x/earn/keeper/vault.go +++ b/x/earn/keeper/vault.go @@ -6,29 +6,6 @@ import ( "github.com/kava-labs/kava/x/earn/types" ) -// ViewVaultKeeper defines the read-only methods used for querying vaults. -type ViewVaultKeeper interface { - // GetVaultTotalSupplied returns the total balance supplied to a vault. This - // may not necessarily be the current value of the vault, as it is the sum - // of the supplied denom. - GetVaultTotalSupplied(ctx sdk.Context, denom string) (sdk.Coin, error) - - // GetVaultTotalValue returns the total **value** of all coins in a vault, - // i.e. the realizable total value denominated by GetDenom() if the vault - // were to liquidate its entire strategies. - GetVaultTotalValue(ctx sdk.Context, denom string) (sdk.Coin, error) - - // GetVaultAccountSupplied returns the supplied amount for a single address - // within the vault. - GetVaultAccountSupplied(ctx sdk.Context, denom string, acc sdk.AccAddress) (sdk.Coin, error) - - // GetVaultAccountValue returns the value of a single address within a vault - // if the account were to withdraw their entire balance. - GetVaultAccountValue(ctx sdk.Context, denom string, acc sdk.AccAddress) (sdk.Coin, error) -} - -var _ ViewVaultKeeper = (*Keeper)(nil) - // GetVaultTotalSupplied 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. @@ -47,6 +24,10 @@ func (k *Keeper) GetVaultTotalSupplied( // GetTotalValue returns the total **value** of all coins in this vault, // i.e. the realizable total value denominated by GetDenom() if the vault // were to liquidate its entire strategies. +// +// **Note:** This does not include the tokens held in bank by the module +// account. If it were to be included, also note that the module account is +// unblocked and can receive funds from bank sends. func (k *Keeper) GetVaultTotalValue( ctx sdk.Context, denom string, @@ -61,19 +42,18 @@ func (k *Keeper) GetVaultTotalValue( return sdk.Coin{}, types.ErrInvalidVaultStrategy } - return strategy.GetEstimatedTotalAssets(enabledVault.Denom) + return strategy.GetEstimatedTotalAssets(ctx, enabledVault.Denom) } // GetVaultAccountSupplied returns the supplied amount for a single address // within a vault. func (k *Keeper) GetVaultAccountSupplied( ctx sdk.Context, - denom string, acc sdk.AccAddress, -) (sdk.Coin, error) { - vaultShareRecord, found := k.GetVaultShareRecord(ctx, denom, acc) +) (sdk.Coins, error) { + vaultShareRecord, found := k.GetVaultShareRecord(ctx, acc) if !found { - return sdk.Coin{}, types.ErrVaultShareRecordNotFound + return sdk.Coins{}, types.ErrVaultShareRecordNotFound } return vaultShareRecord.AmountSupplied, nil @@ -91,7 +71,7 @@ func (k *Keeper) GetVaultAccountValue( return sdk.Coin{}, err } - accSupplied, err := k.GetVaultAccountSupplied(ctx, denom, acc) + accSupplied, err := k.GetVaultAccountSupplied(ctx, acc) if err != nil { return sdk.Coin{}, err } @@ -101,12 +81,12 @@ func (k *Keeper) GetVaultAccountValue( return sdk.Coin{}, err } - // percent of vault account ownership = accountSupply / totalSupply - // value of vault account ownership = percentOwned * totalValue - vaultShare := accSupplied.Amount.Quo(totalSupplied.Amount) - shareValue := vaultTotalValue.Amount.Mul(vaultShare) + // 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, shareValue), nil + return sdk.NewCoin(denom, shareValueDec.TruncateInt()), nil } // ---------------------------------------------------------------------------- @@ -163,12 +143,11 @@ func (k *Keeper) SetVaultRecord(ctx sdk.Context, record types.VaultRecord) { // account. func (k *Keeper) GetVaultShareRecord( ctx sdk.Context, - vaultDenom string, acc sdk.AccAddress, ) (types.VaultShareRecord, bool) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) - bz := store.Get(types.DepositorVaultSharesKey(acc, vaultDenom)) + bz := store.Get(types.DepositorVaultSharesKey(acc)) if bz == nil { return types.VaultShareRecord{}, false } @@ -187,7 +166,7 @@ func (k *Keeper) UpdateVaultShareRecord( record types.VaultShareRecord, ) { if record.AmountSupplied.IsZero() { - k.DeleteVaultShareRecord(ctx, record.AmountSupplied.Denom, record.Depositor) + k.DeleteVaultShareRecord(ctx, record.Depositor) } else { k.SetVaultShareRecord(ctx, record) } @@ -197,11 +176,10 @@ func (k *Keeper) UpdateVaultShareRecord( // account. func (k *Keeper) DeleteVaultShareRecord( ctx sdk.Context, - vaultDenom string, acc sdk.AccAddress, ) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) - store.Delete(types.DepositorVaultSharesKey(acc, vaultDenom)) + 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. @@ -209,7 +187,7 @@ func (k *Keeper) SetVaultShareRecord( ctx sdk.Context, record types.VaultShareRecord, ) { - store := prefix.NewStore(ctx.KVStore(k.key), types.VaultRecordKeyPrefix) + store := prefix.NewStore(ctx.KVStore(k.key), types.VaultShareRecordKeyPrefix) bz := k.cdc.MustMarshal(&record) - store.Set(types.DepositorVaultSharesKey(record.Depositor, record.AmountSupplied.Denom), bz) + store.Set(types.DepositorVaultSharesKey(record.Depositor), bz) } diff --git a/x/earn/keeper/vault_test.go b/x/earn/keeper/vault_test.go index 31687c6f..ca84c527 100644 --- a/x/earn/keeper/vault_test.go +++ b/x/earn/keeper/vault_test.go @@ -28,7 +28,7 @@ func (suite *vaultTestSuite) TestGetVaultTotalSupplied() { startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -76,18 +76,18 @@ func (suite *vaultTestSuite) TestGetVaultAccountSupplied() { deposit1Amount := sdk.NewInt64Coin(vaultDenom, 100) deposit2Amount := sdk.NewInt64Coin(vaultDenom, 100) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0) acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1) // Before deposit, account supplied is 0 - _, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, vaultDenom, acc1.GetAddress()) + _, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc1.GetAddress()) suite.Require().Error(err) suite.Require().ErrorIs(err, types.ErrVaultShareRecordNotFound) - _, err = suite.Keeper.GetVaultAccountSupplied(suite.Ctx, vaultDenom, acc2.GetAddress()) + _, err = suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc2.GetAddress()) suite.Require().Error(err) suite.Require().ErrorIs(err, types.ErrVaultShareRecordNotFound) @@ -101,15 +101,15 @@ func (suite *vaultTestSuite) TestGetVaultAccountSupplied() { // Check balances - vaultAcc1Supplied, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, vaultDenom, acc1.GetAddress()) + vaultAcc1Supplied, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc1.GetAddress()) suite.Require().NoError(err) - vaultAcc2Supplied, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, vaultDenom, acc2.GetAddress()) + vaultAcc2Supplied, err := suite.Keeper.GetVaultAccountSupplied(suite.Ctx, acc2.GetAddress()) suite.Require().NoError(err) // Account supply only includes the deposit from respective accounts - suite.Equal(deposit1Amount, vaultAcc1Supplied) - suite.Equal(deposit1Amount, vaultAcc2Supplied) + suite.Equal(sdk.NewCoins(deposit1Amount), vaultAcc1Supplied) + suite.Equal(sdk.NewCoins(deposit1Amount), vaultAcc2Supplied) } func (suite *vaultTestSuite) TestGetVaultAccountValue() { @@ -119,16 +119,14 @@ func (suite *vaultTestSuite) TestGetVaultAccountValue() { acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount) suite.Require().NoError(err) - suite.T().Skip("TODO: After strategy GetEstimatedTotalAssets implemented") - - _, err = suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc.GetAddress()) - suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrVaultShareRecordNotFound) + accValue, err := suite.Keeper.GetVaultAccountValue(suite.Ctx, vaultDenom, acc.GetAddress()) + suite.Require().NoError(err) + suite.Equal(depositAmount, accValue, "value should be same as deposit amount") } func (suite *vaultTestSuite) TestGetVaultAccountValue_VaultNotFound() { @@ -148,7 +146,7 @@ func (suite *vaultTestSuite) TestGetVaultAccountValue_ShareNotFound() { acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0) acc2 := suite.CreateAccount(sdk.NewCoins(startBalance), 1) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) // Deposit from acc1 so that vault record exists err := suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), depositAmount) @@ -202,19 +200,19 @@ func (suite *vaultTestSuite) TestGetVaultShareRecord() { depositAmount := sdk.NewInt64Coin(vaultDenom, 100) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) - record := types.NewVaultShareRecord(acc.GetAddress(), vaultDenom) + record := types.NewVaultShareRecord(acc.GetAddress()) // Check share doesn't exist before deposit - _, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, vaultDenom, acc.GetAddress()) + _, 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 = depositAmount + 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, vaultDenom, acc.GetAddress()) + stateRecord, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, acc.GetAddress()) suite.Require().True(found) suite.Require().Equal(record, stateRecord) } @@ -225,21 +223,19 @@ func (suite *vaultTestSuite) TestUpdateVaultShareRecord() { depositAmount := sdk.NewInt64Coin(vaultDenom, 100) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) - record := types.NewVaultShareRecord(acc.GetAddress(), vaultDenom) - - record.AmountSupplied = depositAmount + record := types.NewVaultShareRecord(acc.GetAddress(), depositAmount) // Update vault suite.Keeper.UpdateVaultShareRecord(suite.Ctx, record) - stateRecord, found := suite.Keeper.GetVaultShareRecord(suite.Ctx, vaultDenom, acc.GetAddress()) + 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.NewInt64Coin("usdx", 0) + record.AmountSupplied = sdk.NewCoins() suite.Keeper.UpdateVaultShareRecord(suite.Ctx, record) - _, found = suite.Keeper.GetVaultShareRecord(suite.Ctx, vaultDenom, acc.GetAddress()) + _, 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/withdraw.go b/x/earn/keeper/withdraw.go index 02f8ed88..20bdc632 100644 --- a/x/earn/keeper/withdraw.go +++ b/x/earn/keeper/withdraw.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -9,74 +11,106 @@ import ( // Withdraw removes the amount of supplied tokens from a vault and transfers it // back to the account. -func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, amount sdk.Coin) error { +func (k *Keeper) Withdraw(ctx sdk.Context, from sdk.AccAddress, wantAmount sdk.Coin) error { // Get AllowedVault, if not found (not a valid vault), return error - allowedVault, found := k.GetAllowedVault(ctx, amount.Denom) + allowedVault, found := k.GetAllowedVault(ctx, wantAmount.Denom) if !found { return types.ErrInvalidVaultDenom } - if amount.IsZero() { + if wantAmount.IsZero() { return types.ErrInsufficientAmount } - // Check if VaultRecord exists, return error if not exist as it's empty - vaultRecord, found := k.GetVaultRecord(ctx, amount.Denom) + // Check if VaultRecord exists + vaultRecord, found := k.GetVaultRecord(ctx, wantAmount.Denom) if !found { return types.ErrVaultRecordNotFound } - // Get VaultShareRecord for account, create if not exist - vaultShareRecord, found := k.GetVaultShareRecord(ctx, amount.Denom, from) + // 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 } - // Check if VaultShareRecord has enough supplied to withdraw - if vaultShareRecord.AmountSupplied.Amount.LT(amount.Amount) { + // 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()) + + // Check if account is not withdrawing more than they have + // account value < want withdraw amount + if vaultAccValue.Amount.LT(wantAmount.Amount) { return sdkerrors.Wrapf( - types.ErrInvalidShares, - "withdraw of %s shares greater than %s shares supplied", - amount, - vaultShareRecord.AmountSupplied, + types.ErrInsufficientValue, + "account vault value of %s is less than %s desired withdraw amount", + vaultAccValue, + wantAmount, ) } - // Send coins back to account - if err := k.bankKeeper.SendCoinsFromModuleToAccount( - ctx, - types.ModuleName, - from, - sdk.NewCoins(amount), - ); err != nil { - return err - } - - // Decrement VaultRecord and VaultShareRecord supplies - vaultRecord.TotalSupply = vaultRecord.TotalSupply.Sub(amount) - vaultShareRecord.AmountSupplied = vaultShareRecord.AmountSupplied.Sub(amount) - - // Update VaultRecord and VaultShareRecord, deletes if zero supply - k.UpdateVaultRecord(ctx, vaultRecord) - k.UpdateVaultShareRecord(ctx, vaultShareRecord) - // Get the strategy for the vault strategy, err := k.GetStrategy(allowedVault.VaultStrategy) if err != nil { return err } - // Deposit to the strategy - if err := strategy.Withdraw(amount); err != nil { + // 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 { + return fmt.Errorf("failed to withdraw from strategy: %w", err) + } + + // Send coins back to account, must withdraw from strategy first or the + // module account may not have any funds to send. + if err := k.bankKeeper.SendCoinsFromModuleToAccount( + ctx, + types.ModuleName, + from, + sdk.NewCoins(wantAmount), + ); 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()) + + // Decrement VaultRecord and VaultShareRecord supplies + vaultRecord.TotalSupply = vaultRecord.TotalSupply.Sub(sharesWithdrawn) + vaultShareRecord.AmountSupplied = vaultShareRecord.AmountSupplied.Sub(sdk.NewCoins(sharesWithdrawn)) + + // Update VaultRecord and VaultShareRecord, deletes if zero supply + k.UpdateVaultRecord(ctx, vaultRecord) + k.UpdateVaultShareRecord(ctx, vaultShareRecord) + ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeVaultWithdraw, - sdk.NewAttribute(types.AttributeKeyVaultDenom, amount.Denom), + sdk.NewAttribute(types.AttributeKeyVaultDenom, wantAmount.Denom), sdk.NewAttribute(types.AttributeKeyOwner, from.String()), - sdk.NewAttribute(sdk.AttributeKeyAmount, amount.Amount.String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, wantAmount.Amount.String()), ), ) diff --git a/x/earn/keeper/withdraw_test.go b/x/earn/keeper/withdraw_test.go index a2955b4f..7d4401a5 100644 --- a/x/earn/keeper/withdraw_test.go +++ b/x/earn/keeper/withdraw_test.go @@ -24,11 +24,11 @@ func TestWithdrawTestSuite(t *testing.T) { } func (suite *withdrawTestSuite) TestWithdraw_NoVaultRecord() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) withdrawAmount := sdk.NewInt64Coin(vaultDenom, 100) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -49,13 +49,13 @@ func (suite *withdrawTestSuite) TestWithdraw_NoVaultRecord() { } func (suite *withdrawTestSuite) TestWithdraw_NoVaultShareRecord() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) acc1DepositAmount := sdk.NewCoin(vaultDenom, sdk.NewInt(100)) acc2WithdrawAmount := sdk.NewInt64Coin(vaultDenom, 100) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) // Create deposit from acc1 so the VaultRecord exists in state acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -75,18 +75,17 @@ func (suite *withdrawTestSuite) TestWithdraw_NoVaultShareRecord() { sdk.NewCoins(startBalance), ) - suite.ModuleAccountBalanceEqual( - sdk.NewCoins(acc1DepositAmount), - ) + suite.VaultTotalValuesEqual(sdk.NewCoins(acc1DepositAmount)) + suite.VaultTotalSuppliedEqual(sdk.NewCoins(acc1DepositAmount)) } func (suite *withdrawTestSuite) TestWithdraw_ExceedBalance() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) withdrawAmount := sdk.NewInt64Coin(vaultDenom, 200) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -95,7 +94,7 @@ func (suite *withdrawTestSuite) TestWithdraw_ExceedBalance() { err = suite.Keeper.Withdraw(suite.Ctx, acc.GetAddress(), withdrawAmount) suite.Require().Error(err) - suite.Require().ErrorIs(err, types.ErrInvalidShares) + suite.Require().ErrorIs(err, types.ErrInsufficientValue) // Balances still the same after deposit suite.AccountBalanceEqual( @@ -103,17 +102,16 @@ func (suite *withdrawTestSuite) TestWithdraw_ExceedBalance() { sdk.NewCoins(startBalance.Sub(depositAmount)), ) - suite.ModuleAccountBalanceEqual( - sdk.NewCoins(depositAmount), - ) + suite.VaultTotalValuesEqual(sdk.NewCoins(depositAmount)) + suite.VaultTotalSuppliedEqual(sdk.NewCoins(depositAmount)) } func (suite *withdrawTestSuite) TestWithdraw_Zero() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) withdrawAmount := sdk.NewInt64Coin(vaultDenom, 0) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -134,7 +132,7 @@ func (suite *withdrawTestSuite) TestWithdraw_Zero() { } func (suite *withdrawTestSuite) TestWithdraw_InvalidVault() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) withdrawAmount := sdk.NewInt64Coin(vaultDenom, 1001) @@ -159,12 +157,12 @@ func (suite *withdrawTestSuite) TestWithdraw_InvalidVault() { } func (suite *withdrawTestSuite) TestWithdraw_FullBalance() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) withdrawAmount := sdk.NewInt64Coin(vaultDenom, 100) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) @@ -186,12 +184,12 @@ func (suite *withdrawTestSuite) TestWithdraw_FullBalance() { } func (suite *withdrawTestSuite) TestWithdraw_Partial() { - vaultDenom := "busd" + vaultDenom := "usdx" startBalance := sdk.NewInt64Coin(vaultDenom, 1000) depositAmount := sdk.NewInt64Coin(vaultDenom, 100) partialWithdrawAmount := sdk.NewInt64Coin(vaultDenom, 50) - suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_STABLECOIN_STAKERS) + suite.CreateVault(vaultDenom, types.STRATEGY_TYPE_HARD) acc := suite.CreateAccount(sdk.NewCoins(startBalance), 0) diff --git a/x/earn/testutil/suite.go b/x/earn/testutil/suite.go index f98e04c4..53b4bd27 100644 --- a/x/earn/testutil/suite.go +++ b/x/earn/testutil/suite.go @@ -3,16 +3,23 @@ package testutil import ( "fmt" "reflect" + "time" "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/earn/keeper" "github.com/kava-labs/kava/x/earn/types" + "github.com/kava-labs/kava/x/hard" + + hardkeeper "github.com/kava-labs/kava/x/hard/keeper" + hardtypes "github.com/kava-labs/kava/x/hard/types" + pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" + savingskeeper "github.com/kava-labs/kava/x/savings/keeper" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - BankKeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/stretchr/testify/suite" abci "github.com/tendermint/tendermint/abci/types" @@ -20,19 +27,93 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" ) -// Suite implements a test suite for the swap module integration tests +// Suite implements a test suite for the earn module integration tests type Suite struct { suite.Suite Keeper keeper.Keeper App app.TestApp Ctx sdk.Context - BankKeeper BankKeeper.Keeper + BankKeeper bankkeeper.Keeper AccountKeeper authkeeper.AccountKeeper + + // Strategy Keepers + HardKeeper hardkeeper.Keeper + SavingsKeeper savingskeeper.Keeper } // SetupTest instantiates a new app, keepers, and sets suite state func (suite *Suite) SetupTest() { + // Pricefeed required for withdrawing from hard + pricefeedGS := pricefeedtypes.GenesisState{ + Params: pricefeedtypes.Params{ + Markets: []pricefeedtypes.Market{ + {MarketID: "usdx:usd", BaseAsset: "usdx", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + {MarketID: "kava:usd", BaseAsset: "kava", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + {MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + PostedPrices: []pricefeedtypes.PostedPrice{ + { + MarketID: "usdx:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("1.00"), + Expiry: time.Now().Add(100 * time.Hour), + }, + { + MarketID: "kava:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("2.00"), + Expiry: time.Now().Add(100 * time.Hour), + }, + { + MarketID: "bnb:usd", + OracleAddress: sdk.AccAddress{}, + Price: sdk.MustNewDecFromStr("10.00"), + Expiry: time.Now().Add(100 * time.Hour), + }, + }, + } + + hardGS := hardtypes.NewGenesisState(hardtypes.NewParams( + hardtypes.MoneyMarkets{ + hardtypes.NewMoneyMarket( + "usdx", + hardtypes.NewBorrowLimit( + true, + sdk.MustNewDecFromStr("20000000"), + sdk.MustNewDecFromStr("1"), + ), + "usdx:usd", + sdk.NewInt(1000000), + hardtypes.NewInterestRateModel( + sdk.MustNewDecFromStr("0.05"), + sdk.MustNewDecFromStr("2"), + sdk.MustNewDecFromStr("0.8"), + sdk.MustNewDecFromStr("10"), + ), + sdk.MustNewDecFromStr("0.05"), + sdk.ZeroDec(), + ), + }, + sdk.NewDec(10), + ), + hardtypes.DefaultAccumulationTimes, + hardtypes.DefaultDeposits, + hardtypes.DefaultBorrows, + hardtypes.DefaultTotalSupplied, + hardtypes.DefaultTotalBorrowed, + hardtypes.DefaultTotalReserves, + ) + tApp := app.NewTestApp() + + tApp.InitializeFromGenesisStates( + app.GenesisState{ + pricefeedtypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&pricefeedGS), + hardtypes.ModuleName: tApp.AppCodec().MustMarshalJSON(&hardGS), + }, + ) + ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) suite.Ctx = ctx @@ -40,6 +121,11 @@ func (suite *Suite) SetupTest() { suite.Keeper = tApp.GetEarnKeeper() suite.BankKeeper = tApp.GetBankKeeper() suite.AccountKeeper = tApp.GetAccountKeeper() + + suite.HardKeeper = tApp.GetHardKeeper() + suite.SavingsKeeper = tApp.GetSavingsKeeper() + + hard.BeginBlocker(suite.Ctx, suite.HardKeeper) } // GetEvents returns emitted events on the sdk context @@ -47,16 +133,16 @@ func (suite *Suite) GetEvents() sdk.Events { return suite.Ctx.EventManager().Events() } -// AddCoinsToModule adds coins to the swap module account +// AddCoinsToModule adds coins to the earn module account func (suite *Suite) AddCoinsToModule(amount sdk.Coins) { // Does not use suite.BankKeeper.MintCoins as module account would not have permission to mint err := simapp.FundModuleAccount(suite.BankKeeper, suite.Ctx, types.ModuleName, amount) suite.Require().NoError(err) } -// RemoveCoinsFromModule removes coins to the swap module account +// RemoveCoinsFromModule removes coins to the earn module account func (suite *Suite) RemoveCoinsFromModule(amount sdk.Coins) { - // Swap module does not have BurnCoins permission so we need to transfer to gov first to burn + // Earn module does not have BurnCoins permission so we need to transfer to gov first to burn err := suite.BankKeeper.SendCoinsFromModuleToModule(suite.Ctx, types.ModuleAccountName, govtypes.ModuleName, amount) suite.Require().NoError(err) err = suite.BankKeeper.BurnCoins(suite.Ctx, govtypes.ModuleName, amount) @@ -96,12 +182,12 @@ func (suite *Suite) CreateVault(vaultDenom string, vaultStrategy types.StrategyT vault := types.NewAllowedVault(vaultDenom, vaultStrategy) suite.Require().NoError(vault.Validate()) - // allowedVaults := suite.Keeper.GetAllowedVaults(suite.Ctx) - // allowedVaults = append(allowedVaults, vault) + allowedVaults := suite.Keeper.GetAllowedVaults(suite.Ctx) + allowedVaults = append(allowedVaults, vault) suite.Keeper.SetParams( suite.Ctx, - types.NewParams(types.AllowedVaults{vault}), + types.NewParams(allowedVaults), ) } @@ -111,7 +197,7 @@ func (suite *Suite) AccountBalanceEqual(addr sdk.AccAddress, coins sdk.Coins) { suite.Equal(coins, balance, fmt.Sprintf("expected account balance to equal coins %s, but got %s", coins, balance)) } -// ModuleAccountBalanceEqual asserts that the swap module account balance matches the provided coins +// ModuleAccountBalanceEqual asserts that the earn module account balance matches the provided coins func (suite *Suite) ModuleAccountBalanceEqual(coins sdk.Coins) { balance := suite.BankKeeper.GetAllBalances( suite.Ctx, @@ -120,6 +206,58 @@ func (suite *Suite) ModuleAccountBalanceEqual(coins sdk.Coins) { suite.Equal(coins, balance, fmt.Sprintf("expected module account balance to equal coins %s, but got %s", coins, balance)) } +// ---------------------------------------------------------------------------- +// Earn + +func (suite *Suite) VaultTotalValuesEqual(expected sdk.Coins) { + for _, coin := range expected { + vaultBal, err := suite.Keeper.GetVaultTotalValue(suite.Ctx, coin.Denom) + suite.Require().NoError(err, "failed to get vault balance") + suite.Require().Equal(coin, vaultBal) + } +} + +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) AccountTotalSuppliedEqual(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) + } +} + +// ---------------------------------------------------------------------------- +// Hard + +func (suite *Suite) HardDepositAmountEqual(expected sdk.Coins) { + macc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + + hardDeposit, found := suite.HardKeeper.GetSyncedDeposit(suite.Ctx, macc.GetAddress()) + if expected.IsZero() { + suite.Require().False(found) + return + } + + suite.Require().True(found, "hard should have a deposit") + suite.Require().Equalf( + expected, + hardDeposit.Amount, + "hard should have a deposit with the amount %v", + expected, + ) +} + +// ---------------------------------------------------------------------------- + // EventsContains asserts that the expected event is in the provided events func (suite *Suite) EventsContains(events sdk.Events, expectedEvent sdk.Event) { foundMatch := false diff --git a/x/earn/types/errors.go b/x/earn/types/errors.go index 88a286d9..dfd2e8fb 100644 --- a/x/earn/types/errors.go +++ b/x/earn/types/errors.go @@ -6,10 +6,11 @@ import ( // earn module errors var ( - ErrInvalidVaultDenom = sdkerrors.Register(ModuleName, 2, "invalid vault denom") - ErrInvalidVaultStrategy = sdkerrors.Register(ModuleName, 3, "invalid vault strategy") - ErrInsufficientAmount = sdkerrors.Register(ModuleName, 4, "insufficient amount") - ErrInvalidShares = sdkerrors.Register(ModuleName, 5, "invalid shares") - ErrVaultRecordNotFound = sdkerrors.Register(ModuleName, 6, "vault record not found") - ErrVaultShareRecordNotFound = sdkerrors.Register(ModuleName, 7, "vault share record not found") + ErrInvalidVaultDenom = sdkerrors.Register(ModuleName, 2, "invalid vault denom") + ErrInvalidVaultStrategy = sdkerrors.Register(ModuleName, 3, "invalid vault strategy") + ErrInsufficientAmount = sdkerrors.Register(ModuleName, 4, "insufficient amount") + ErrInsufficientValue = sdkerrors.Register(ModuleName, 5, "insufficient vault account value") + ErrVaultRecordNotFound = sdkerrors.Register(ModuleName, 6, "vault record not found") + ErrVaultShareRecordNotFound = sdkerrors.Register(ModuleName, 7, "vault share record not found") + ErrStrategyDenomNotSupported = sdkerrors.Register(ModuleName, 8, "denom not supported for strategy") ) diff --git a/x/earn/types/events.go b/x/earn/types/events.go index 00a12506..bcb38000 100644 --- a/x/earn/types/events.go +++ b/x/earn/types/events.go @@ -1,6 +1,6 @@ package types -// Event types for swap module +// Event types for earn module const ( AttributeValueCategory = ModuleName EventTypeVaultDeposit = "vault_deposit" diff --git a/x/earn/types/expected_keepers.go b/x/earn/types/expected_keepers.go index f9ea148e..f8403222 100644 --- a/x/earn/types/expected_keepers.go +++ b/x/earn/types/expected_keepers.go @@ -3,6 +3,9 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" + + hardtypes "github.com/kava-labs/kava/x/hard/types" + savingstypes "github.com/kava-labs/kava/x/savings/types" ) // AccountKeeper defines the expected account keeper @@ -21,3 +24,19 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error } + +// HardKeeper defines the expected interface needed for the hard strategy. +type HardKeeper interface { + Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error + Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error + + GetSyncedDeposit(ctx sdk.Context, depositor sdk.AccAddress) (hardtypes.Deposit, bool) +} + +// SavingsKeeper defines the expected interface needed for the savings strategy. +type SavingsKeeper interface { + Deposit(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error + Withdraw(ctx sdk.Context, depositor sdk.AccAddress, coins sdk.Coins) error + + GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (savingstypes.Deposit, bool) +} diff --git a/x/earn/types/genesis.pb.go b/x/earn/types/genesis.pb.go index 74639846..c71187d4 100644 --- a/x/earn/types/genesis.pb.go +++ b/x/earn/types/genesis.pb.go @@ -23,7 +23,7 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package -// GenesisState defines the swap module's genesis state. +// GenesisState defines the earn module's genesis state. type GenesisState struct { // params defines all the paramaters related to earn Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` diff --git a/x/earn/types/keys.go b/x/earn/types/keys.go index ae5bd25d..8d3bba56 100644 --- a/x/earn/types/keys.go +++ b/x/earn/types/keys.go @@ -24,10 +24,8 @@ const ( // key prefixes for store var ( - VaultRecordKeyPrefix = []byte{0x01} // denom -> vault - VaultSharePrefix = []byte{0x02} - - sep = []byte("|") + VaultRecordKeyPrefix = []byte{0x01} // denom -> vault + VaultShareRecordKeyPrefix = []byte{0x02} // depositor address -> vault shares ) // Vault returns a key generated from a vault denom @@ -35,14 +33,7 @@ func VaultKey(denom string) []byte { return []byte(denom) } -// DepositorVaultSharesKey returns a key from a depositor and vault denom -func DepositorVaultSharesKey(depositor sdk.AccAddress, vaultDenom string) []byte { - return createKey(depositor, sep, []byte(vaultDenom)) -} - -func createKey(bytes ...[]byte) (r []byte) { - for _, b := range bytes { - r = append(r, b...) - } - return +// DepositorVaultSharesKey returns a key from a depositor address +func DepositorVaultSharesKey(depositor sdk.AccAddress) []byte { + return depositor.Bytes() } diff --git a/x/earn/types/strategy.pb.go b/x/earn/types/strategy.pb.go index 99beb5c0..6138e542 100644 --- a/x/earn/types/strategy.pb.go +++ b/x/earn/types/strategy.pb.go @@ -26,25 +26,21 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type StrategyType int32 const ( - STRATEGY_TYPE_UNKNOWN StrategyType = 0 - STRATEGY_TYPE_KAVA_STAKERS StrategyType = 1 - // USDC / BUSD vaults use the same strategy but with the denom set in VaultRecord - STRATEGY_TYPE_STABLECOIN_STAKERS StrategyType = 2 - STRATEGY_TYPE_KAVA_FOUNDATION StrategyType = 3 + STRATEGY_TYPE_UNKNOWN StrategyType = 0 + STRATEGY_TYPE_HARD StrategyType = 1 + STRATEGY_TYPE_SAVINGS StrategyType = 2 ) var StrategyType_name = map[int32]string{ 0: "STRATEGY_TYPE_UNKNOWN", - 1: "STRATEGY_TYPE_KAVA_STAKERS", - 2: "STRATEGY_TYPE_STABLECOIN_STAKERS", - 3: "STRATEGY_TYPE_KAVA_FOUNDATION", + 1: "STRATEGY_TYPE_HARD", + 2: "STRATEGY_TYPE_SAVINGS", } var StrategyType_value = map[string]int32{ - "STRATEGY_TYPE_UNKNOWN": 0, - "STRATEGY_TYPE_KAVA_STAKERS": 1, - "STRATEGY_TYPE_STABLECOIN_STAKERS": 2, - "STRATEGY_TYPE_KAVA_FOUNDATION": 3, + "STRATEGY_TYPE_UNKNOWN": 0, + "STRATEGY_TYPE_HARD": 1, + "STRATEGY_TYPE_SAVINGS": 2, } func (x StrategyType) String() string { @@ -62,23 +58,21 @@ func init() { func init() { proto.RegisterFile("kava/earn/v1beta1/strategy.proto", fileDescriptor_257c4968dd48fa09) } var fileDescriptor_257c4968dd48fa09 = []byte{ - // 278 bytes of a gzipped FileDescriptorProto + // 243 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xc8, 0x4e, 0x2c, 0x4b, 0xd4, 0x4f, 0x4d, 0x2c, 0xca, 0xd3, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x2f, 0x2e, 0x29, 0x4a, 0x2c, 0x49, 0x4d, 0xaf, 0xd4, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x04, 0xa9, 0xd0, 0x03, 0xa9, 0xd0, 0x83, 0xaa, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xea, 0x83, 0x58, 0x10, 0x85, 0x52, 0x72, 0xc9, 0xf9, 0xc5, 0xb9, 0xf9, 0xc5, 0xfa, 0x49, 0x89, 0xc5, 0xa9, 0x70, 0xc3, 0x92, 0xf3, 0x33, 0xf3, 0xa0, 0xf2, 0x92, 0x10, 0xf9, 0x78, 0x88, 0x46, 0x08, 0x07, - 0x22, 0xa5, 0x35, 0x83, 0x91, 0x8b, 0x27, 0x18, 0x6a, 0x6d, 0x48, 0x65, 0x41, 0xaa, 0x90, 0x24, - 0x97, 0x68, 0x70, 0x48, 0x90, 0x63, 0x88, 0xab, 0x7b, 0x64, 0x7c, 0x48, 0x64, 0x80, 0x6b, 0x7c, - 0xa8, 0x9f, 0xb7, 0x9f, 0x7f, 0xb8, 0x9f, 0x00, 0x83, 0x90, 0x1c, 0x97, 0x14, 0xaa, 0x94, 0xb7, - 0x63, 0x98, 0x63, 0x7c, 0x70, 0x88, 0xa3, 0xb7, 0x6b, 0x50, 0xb0, 0x00, 0xa3, 0x90, 0x0a, 0x97, - 0x02, 0xaa, 0x7c, 0x70, 0x88, 0xa3, 0x93, 0x8f, 0xab, 0xb3, 0xbf, 0xa7, 0x1f, 0x5c, 0x15, 0x93, - 0x90, 0x22, 0x97, 0x2c, 0x16, 0x53, 0xdc, 0xfc, 0x43, 0xfd, 0x5c, 0x1c, 0x43, 0x3c, 0xfd, 0xfd, - 0x04, 0x98, 0xa5, 0x58, 0x3a, 0x16, 0xcb, 0x31, 0x38, 0x39, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0xe1, - 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, - 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x5a, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, - 0x3e, 0x28, 0x8c, 0x74, 0x73, 0x12, 0x93, 0x8a, 0xc1, 0x2c, 0xfd, 0x0a, 0x48, 0x88, 0x96, 0x54, - 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0xfd, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x85, 0x84, - 0x93, 0x9f, 0x6b, 0x01, 0x00, 0x00, + 0x22, 0xa5, 0x95, 0xc4, 0xc5, 0x13, 0x0c, 0xb5, 0x35, 0xa4, 0xb2, 0x20, 0x55, 0x48, 0x92, 0x4b, + 0x34, 0x38, 0x24, 0xc8, 0x31, 0xc4, 0xd5, 0x3d, 0x32, 0x3e, 0x24, 0x32, 0xc0, 0x35, 0x3e, 0xd4, + 0xcf, 0xdb, 0xcf, 0x3f, 0xdc, 0x4f, 0x80, 0x41, 0x48, 0x8c, 0x4b, 0x08, 0x55, 0xca, 0xc3, 0x31, + 0xc8, 0x45, 0x80, 0x11, 0x53, 0x4b, 0xb0, 0x63, 0x98, 0xa7, 0x9f, 0x7b, 0xb0, 0x00, 0x93, 0x14, + 0x4b, 0xc7, 0x62, 0x39, 0x06, 0x27, 0x87, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x3c, 0x92, 0x63, 0x7c, + 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x0b, 0x8f, 0xe5, 0x18, 0x6e, 0x3c, 0x96, 0x63, + 0x88, 0x52, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x07, 0x79, 0x56, + 0x37, 0x27, 0x31, 0xa9, 0x18, 0xcc, 0xd2, 0xaf, 0x80, 0x04, 0x4d, 0x49, 0x65, 0x41, 0x6a, 0x71, + 0x12, 0x1b, 0xd8, 0xb1, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x62, 0x68, 0x2c, 0xbc, 0x34, + 0x01, 0x00, 0x00, } diff --git a/x/earn/types/vault.go b/x/earn/types/vault.go index 723868d2..ff4d51ba 100644 --- a/x/earn/types/vault.go +++ b/x/earn/types/vault.go @@ -18,11 +18,12 @@ type VaultRecords []VaultRecord type VaultShareRecords []VaultShareRecord -// NewVaultShareRecord returns a new VaultShareRecord with 0 supply. -func NewVaultShareRecord(depositor sdk.AccAddress, vaultDenom string) VaultShareRecord { +// NewVaultShareRecord returns a new VaultShareRecord with the provided supplied +// coins. +func NewVaultShareRecord(depositor sdk.AccAddress, supplied ...sdk.Coin) VaultShareRecord { return VaultShareRecord{ Depositor: depositor, - AmountSupplied: sdk.NewCoin(vaultDenom, sdk.ZeroInt()), + AmountSupplied: sdk.NewCoins(supplied...), } } diff --git a/x/earn/types/vault.pb.go b/x/earn/types/vault.pb.go index 2d1aa65f..ab6127c5 100644 --- a/x/earn/types/vault.pb.go +++ b/x/earn/types/vault.pb.go @@ -140,13 +140,13 @@ func (m *VaultRecord) GetTotalSupply() types.Coin { return types.Coin{} } -// VaultShareRecord defines the shares owned by a depositor and vault. +// VaultShareRecord defines the vault shares owned by a depositor. type VaultShareRecord struct { - // depositor represents the owner of the shares + // 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"` - // amount_supplied represents the total amount a depositor has supplied to the + // AmountSupplied represents the total amount a depositor has supplied to the // vault. The vault is determined by the coin denom. - AmountSupplied types.Coin `protobuf:"bytes,2,opt,name=amount_supplied,json=amountSupplied,proto3" json:"amount_supplied"` + 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"` } func (m *VaultShareRecord) Reset() { *m = VaultShareRecord{} } @@ -189,11 +189,11 @@ func (m *VaultShareRecord) GetDepositor() github_com_cosmos_cosmos_sdk_types.Acc return nil } -func (m *VaultShareRecord) GetAmountSupplied() types.Coin { +func (m *VaultShareRecord) GetAmountSupplied() github_com_cosmos_cosmos_sdk_types.Coins { if m != nil { return m.AmountSupplied } - return types.Coin{} + return nil } func init() { @@ -205,32 +205,34 @@ func init() { func init() { proto.RegisterFile("kava/earn/v1beta1/vault.proto", fileDescriptor_884eb89509fbdc04) } var fileDescriptor_884eb89509fbdc04 = []byte{ - // 398 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x51, 0xcd, 0xae, 0xd2, 0x40, - 0x14, 0x6e, 0x8d, 0x9a, 0x30, 0x20, 0x6a, 0x65, 0x01, 0x24, 0x16, 0xc2, 0xc2, 0xb0, 0xe9, 0x34, - 0xe0, 0x0b, 0x48, 0x4d, 0x0c, 0xeb, 0xd6, 0xb8, 0x70, 0x43, 0xa6, 0x9d, 0xb1, 0x34, 0xb4, 0x3d, - 0x4d, 0x67, 0x8a, 0xf6, 0x2d, 0x7c, 0x18, 0x1f, 0xc1, 0x05, 0x4b, 0xe2, 0xca, 0x15, 0xb9, 0x81, - 0xb7, 0xb8, 0xab, 0x9b, 0xce, 0x0c, 0xdc, 0x9b, 0x90, 0x9b, 0xdc, 0xd5, 0xfc, 0x7c, 0xe7, 0x3b, - 0xdf, 0xf7, 0x9d, 0x83, 0xde, 0x6f, 0xc8, 0x96, 0xb8, 0x8c, 0x94, 0xb9, 0xbb, 0x9d, 0x85, 0x4c, - 0x90, 0x99, 0xbb, 0x25, 0x55, 0x2a, 0x70, 0x51, 0x82, 0x00, 0xeb, 0x6d, 0x03, 0xe3, 0x06, 0xc6, - 0x1a, 0x1e, 0xf6, 0x62, 0x88, 0x41, 0xa2, 0x6e, 0x73, 0x53, 0x85, 0x43, 0x3b, 0x02, 0x9e, 0x01, - 0x77, 0x43, 0xc2, 0xd9, 0xa5, 0x53, 0x04, 0x49, 0xae, 0xf1, 0x81, 0xc2, 0x57, 0x8a, 0xa8, 0x1e, - 0x1a, 0x1a, 0x5f, 0x5b, 0xe0, 0xa2, 0x24, 0x82, 0xc5, 0xb5, 0xaa, 0x98, 0xa4, 0xa8, 0xb3, 0x48, - 0x53, 0xf8, 0xc9, 0xe8, 0xb7, 0xc6, 0x9b, 0xd5, 0x43, 0x2f, 0x28, 0xcb, 0x21, 0xeb, 0x9b, 0x63, - 0x73, 0xda, 0xf2, 0xd5, 0xc3, 0xfa, 0x82, 0xba, 0xd2, 0xfa, 0xea, 0xcc, 0xee, 0x3f, 0x1b, 0x9b, - 0xd3, 0xee, 0x7c, 0x84, 0xaf, 0x42, 0xe0, 0x40, 0x97, 0x7c, 0xad, 0x0b, 0xe6, 0xbf, 0x92, 0xb4, - 0xf3, 0xd7, 0x24, 0x46, 0x6d, 0x29, 0xe3, 0xb3, 0x08, 0x4a, 0xfa, 0x88, 0x98, 0x87, 0x3a, 0x02, - 0x04, 0x49, 0x57, 0xbc, 0x2a, 0x8a, 0x54, 0x49, 0xb5, 0xe7, 0x03, 0xac, 0x93, 0x35, 0x63, 0xb8, - 0x88, 0x7d, 0x86, 0x24, 0xf7, 0x9e, 0xef, 0x0e, 0x23, 0xc3, 0x6f, 0x4b, 0x52, 0x20, 0x39, 0x93, - 0xbf, 0x26, 0x7a, 0x23, 0x95, 0x82, 0x35, 0x29, 0x99, 0x96, 0xfb, 0x81, 0x5a, 0x94, 0x15, 0xc0, - 0x13, 0x01, 0xa5, 0x94, 0xec, 0x78, 0xcb, 0xdb, 0xc3, 0xc8, 0x89, 0x13, 0xb1, 0xae, 0x42, 0x1c, - 0x41, 0xa6, 0xa7, 0xa7, 0x0f, 0x87, 0xd3, 0x8d, 0x2b, 0xea, 0x82, 0x71, 0xbc, 0x88, 0xa2, 0x05, - 0xa5, 0x25, 0xe3, 0xfc, 0xdf, 0x1f, 0xe7, 0x9d, 0x76, 0xa2, 0x7f, 0xbc, 0x5a, 0x30, 0xee, 0xdf, - 0xb7, 0xb6, 0x96, 0xe8, 0x35, 0xc9, 0xa0, 0xca, 0x85, 0x4a, 0x90, 0x30, 0xfa, 0xd4, 0x0c, 0x5d, - 0xc5, 0x0b, 0x34, 0xcd, 0xfb, 0xb4, 0x3b, 0xda, 0xe6, 0xfe, 0x68, 0x9b, 0x37, 0x47, 0xdb, 0xfc, - 0x7d, 0xb2, 0x8d, 0xfd, 0xc9, 0x36, 0xfe, 0x9f, 0x6c, 0xe3, 0xfb, 0x87, 0x07, 0xa6, 0x9b, 0x1d, - 0x38, 0x29, 0x09, 0xb9, 0xbc, 0xb9, 0xbf, 0xd4, 0xc2, 0xa5, 0xf1, 0xf0, 0xa5, 0x5c, 0xf3, 0xc7, - 0xbb, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc6, 0x8b, 0xa9, 0xd7, 0x8d, 0x02, 0x00, 0x00, + // 417 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xbf, 0xae, 0xd3, 0x30, + 0x14, 0xc6, 0x93, 0xcb, 0x1f, 0xe9, 0xba, 0xa5, 0x40, 0xb8, 0x43, 0xef, 0x95, 0x48, 0xa2, 0x0e, + 0xa8, 0x4b, 0x1c, 0xee, 0xe5, 0x05, 0x68, 0x90, 0x10, 0x73, 0x82, 0x18, 0x58, 0x2a, 0x27, 0x36, + 0x69, 0xd4, 0x24, 0x27, 0x8a, 0x9d, 0x42, 0xde, 0x82, 0xe7, 0x60, 0xe6, 0x21, 0x3a, 0x56, 0x4c, + 0x4c, 0x05, 0xb5, 0x2f, 0xc0, 0xcc, 0x84, 0xfc, 0xa7, 0x05, 0xa9, 0x02, 0x31, 0xc5, 0xf6, 0x77, + 0xbe, 0xf3, 0x3b, 0x9f, 0x63, 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, 0x32, 0x96, + 0x32, 0x36, 0xf2, 0xd5, 0x45, 0x0e, 0x39, 0x28, 0x35, 0x94, 0x2b, 0x5d, 0x78, 0xe5, 0x66, 0xc0, + 0x2b, 0xe0, 0x61, 0x4a, 0x38, 0x3b, 0x76, 0xca, 0xa0, 0xa8, 0x8d, 0x7e, 0xa9, 0xf5, 0xb9, 0x36, + 0xea, 0x8d, 0x91, 0xfc, 0xd3, 0x11, 0xb8, 0x68, 0x89, 0x60, 0x79, 0xaf, 0x2b, 0x26, 0x25, 0x1a, + 0xce, 0xca, 0x12, 0xde, 0x33, 0xfa, 0x46, 0xce, 0xe6, 0x5c, 0xa0, 0x3b, 0x94, 0xd5, 0x50, 0x8d, + 0x6d, 0xdf, 0x9e, 0x9e, 0xc7, 0x7a, 0xe3, 0xbc, 0x44, 0x23, 0x35, 0xfa, 0xfc, 0xe0, 0x1e, 0x9f, + 0xf9, 0xf6, 0x74, 0x74, 0xe3, 0xe1, 0x93, 0x10, 0x38, 0x31, 0x25, 0xaf, 0xfb, 0x86, 0xc5, 0xf7, + 0x94, 0xed, 0x70, 0x34, 0xc9, 0xd1, 0x40, 0x61, 0x62, 0x96, 0x41, 0x4b, 0xff, 0x02, 0x8b, 0xd0, + 0x50, 0x80, 0x20, 0xe5, 0x9c, 0x77, 0x4d, 0x53, 0x6a, 0xd4, 0xe0, 0xe6, 0x12, 0x9b, 0x64, 0xf2, + 0x1a, 0x8e, 0xb0, 0x17, 0x50, 0xd4, 0xd1, 0xed, 0xf5, 0xd6, 0xb3, 0xe2, 0x81, 0x32, 0x25, 0xca, + 0x33, 0xf9, 0x61, 0xa3, 0x07, 0x8a, 0x94, 0x2c, 0x48, 0xcb, 0x0c, 0xee, 0x1d, 0x3a, 0xa7, 0xac, + 0x01, 0x5e, 0x08, 0x68, 0x15, 0x72, 0x18, 0xbd, 0xfa, 0xb9, 0xf5, 0x82, 0xbc, 0x10, 0x8b, 0x2e, + 0xc5, 0x19, 0x54, 0xe6, 0xf6, 0xcc, 0x27, 0xe0, 0x74, 0x19, 0x8a, 0xbe, 0x61, 0x1c, 0xcf, 0xb2, + 0x6c, 0x46, 0x69, 0xcb, 0x38, 0xff, 0xf2, 0x39, 0x78, 0x64, 0x26, 0x31, 0x27, 0x51, 0x2f, 0x18, + 0x8f, 0x7f, 0xb7, 0x76, 0x04, 0xba, 0x4f, 0x2a, 0xe8, 0x6a, 0xa1, 0x13, 0x14, 0x8c, 0x8e, 0xcf, + 0xfc, 0x5b, 0xff, 0xce, 0xf0, 0x54, 0x66, 0xf8, 0xf4, 0xcd, 0x9b, 0xfe, 0xc7, 0x30, 0xd2, 0xc0, + 0xe3, 0x91, 0x66, 0x24, 0x06, 0x11, 0x3d, 0x5f, 0xef, 0x5c, 0x7b, 0xb3, 0x73, 0xed, 0xef, 0x3b, + 0xd7, 0xfe, 0xb8, 0x77, 0xad, 0xcd, 0xde, 0xb5, 0xbe, 0xee, 0x5d, 0xeb, 0xed, 0x93, 0x3f, 0x7a, + 0xca, 0xff, 0x15, 0x94, 0x24, 0xe5, 0x6a, 0x15, 0x7e, 0xd0, 0x8f, 0x43, 0xf5, 0x4d, 0xef, 0xaa, + 0x27, 0xf1, 0xec, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x82, 0x2c, 0xf8, 0xd8, 0xb9, 0x02, 0x00, + 0x00, } func (m *AllowedVault) Marshal() (dAtA []byte, err error) { @@ -328,16 +330,20 @@ func (m *VaultShareRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - { - size, err := m.AmountSupplied.MarshalToSizedBuffer(dAtA[:i]) - if err != nil { - return 0, err + if len(m.AmountSupplied) > 0 { + for iNdEx := len(m.AmountSupplied) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.AmountSupplied[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintVault(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 } - i -= size - i = encodeVarintVault(dAtA, i, uint64(size)) } - i-- - dAtA[i] = 0x12 if len(m.Depositor) > 0 { i -= len(m.Depositor) copy(dAtA[i:], m.Depositor) @@ -400,8 +406,12 @@ func (m *VaultShareRecord) Size() (n int) { if l > 0 { n += 1 + l + sovVault(uint64(l)) } - l = m.AmountSupplied.Size() - n += 1 + l + sovVault(uint64(l)) + if len(m.AmountSupplied) > 0 { + for _, e := range m.AmountSupplied { + l = e.Size() + n += 1 + l + sovVault(uint64(l)) + } + } return n } @@ -719,7 +729,8 @@ func (m *VaultShareRecord) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.AmountSupplied.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + m.AmountSupplied = append(m.AmountSupplied, types.Coin{}) + if err := m.AmountSupplied[len(m.AmountSupplied)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex