mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 00:07:51 +00:00 
			
		
		
		
	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
This commit is contained in:
		
							parent
							
								
									ae181604ff
								
							
						
					
					
						commit
						88d4868316
					
				@ -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
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -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 |  |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 <!-- end enums -->
 | 
			
		||||
@ -2809,13 +2808,13 @@ vault.
 | 
			
		||||
<a name="kava.earn.v1beta1.VaultShareRecord"></a>
 | 
			
		||||
 | 
			
		||||
### 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.
 | 
			
		||||
<a name="kava.earn.v1beta1.GenesisState"></a>
 | 
			
		||||
 | 
			
		||||
### GenesisState
 | 
			
		||||
GenesisState defines the swap module's genesis state.
 | 
			
		||||
GenesisState defines the earn module's genesis state.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| Field | Type | Label | Description |
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
}
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										48
									
								
								x/earn/keeper/strategy_hard.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								x/earn/keeper/strategy_hard.go
									
									
									
									
									
										Normal file
									
								
							@ -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))
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										300
									
								
								x/earn/keeper/strategy_hard_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										300
									
								
								x/earn/keeper/strategy_hard_test.go
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
}
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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()),
 | 
			
		||||
		),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
// Event types for swap module
 | 
			
		||||
// Event types for earn module
 | 
			
		||||
const (
 | 
			
		||||
	AttributeValueCategory = ModuleName
 | 
			
		||||
	EventTypeVaultDeposit  = "vault_deposit"
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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"`
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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...),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user