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:
Derrick Lee 2022-07-28 09:39:57 -07:00 committed by GitHub
parent ae181604ff
commit 88d4868316
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 813 additions and 336 deletions

View File

@ -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
}
}

View File

@ -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 |

View File

@ -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];

View File

@ -11,8 +11,6 @@ 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_HARD = 1;
STRATEGY_TYPE_SAVINGS = 2;
}

View File

@ -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];
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

View File

@ -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,
)

View File

@ -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,
}
}

View File

@ -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)

View File

@ -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)

View 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))
}

View 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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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()),
),
)

View File

@ -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)

View File

@ -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

View File

@ -9,7 +9,8 @@ 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")
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")
)

View File

@ -1,6 +1,6 @@
package types
// Event types for swap module
// Event types for earn module
const (
AttributeValueCategory = ModuleName
EventTypeVaultDeposit = "vault_deposit"

View File

@ -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)
}

View File

@ -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"`

View File

@ -25,9 +25,7 @@ const (
// key prefixes for store
var (
VaultRecordKeyPrefix = []byte{0x01} // denom -> vault
VaultSharePrefix = []byte{0x02}
sep = []byte("|")
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()
}

View File

@ -27,24 +27,20 @@ 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_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_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,
}

View File

@ -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...),
}
}

View File

@ -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,8 +330,10 @@ func (m *VaultShareRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.AmountSupplied) > 0 {
for iNdEx := len(m.AmountSupplied) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.AmountSupplied.MarshalToSizedBuffer(dAtA[:i])
size, err := m.AmountSupplied[iNdEx].MarshalToSizedBuffer(dAtA[:i])
if err != nil {
return 0, err
}
@ -338,6 +342,8 @@ func (m *VaultShareRecord) MarshalToSizedBuffer(dAtA []byte) (int, error) {
}
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()
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