mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-14 03:55:19 +00:00
fcfcd36740
* add total supply endpoint to x/liquid * add test of x/liquid total supply query * refactor x/savings test * add total supply endpoint for x/savings (w/ test) * add total supply endpoint for x/earn * handle converting bkava to underlying staked amount * aggregate bkava underlying values in x/earn * aggregate underlying value of bkava in x/savings
905 lines
29 KiB
Go
905 lines
29 KiB
Go
package keeper_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
|
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
"github.com/stretchr/testify/suite"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
|
|
"github.com/kava-labs/kava/app"
|
|
"github.com/kava-labs/kava/x/earn/keeper"
|
|
"github.com/kava-labs/kava/x/earn/testutil"
|
|
"github.com/kava-labs/kava/x/earn/types"
|
|
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
|
|
)
|
|
|
|
type grpcQueryTestSuite struct {
|
|
testutil.Suite
|
|
|
|
queryClient types.QueryClient
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) SetupTest() {
|
|
suite.Suite.SetupTest()
|
|
suite.Keeper.SetParams(suite.Ctx, types.DefaultParams())
|
|
|
|
queryHelper := baseapp.NewQueryServerTestHelper(suite.Ctx, suite.App.InterfaceRegistry())
|
|
types.RegisterQueryServer(queryHelper, keeper.NewQueryServerImpl(suite.Keeper))
|
|
|
|
suite.queryClient = types.NewQueryClient(queryHelper)
|
|
}
|
|
|
|
func TestGrpcQueryTestSuite(t *testing.T) {
|
|
suite.Run(t, new(grpcQueryTestSuite))
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestQueryParams() {
|
|
vaultDenom := "usdx"
|
|
|
|
res, err := suite.queryClient.Params(context.Background(), types.NewQueryParamsRequest())
|
|
suite.Require().NoError(err)
|
|
// ElementsMatch instead of Equal because AllowedVaults{} != AllowedVaults(nil)
|
|
suite.Require().ElementsMatch(types.DefaultParams().AllowedVaults, res.Params.AllowedVaults)
|
|
|
|
// Add vault to params
|
|
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
|
|
// 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.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil),
|
|
},
|
|
res.Params.AllowedVaults,
|
|
)
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestVaults_ZeroSupply() {
|
|
// Add vaults
|
|
suite.CreateVault("usdx", types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault("busd", types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
|
|
suite.Run("single", func() {
|
|
res, err := suite.queryClient.Vault(context.Background(), types.NewQueryVaultRequest("usdx"))
|
|
suite.Require().NoError(err)
|
|
suite.Require().Equal(
|
|
types.VaultResponse{
|
|
Denom: "usdx",
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: sdk.NewDec(0).String(),
|
|
TotalValue: sdk.NewInt(0),
|
|
},
|
|
res.Vault,
|
|
)
|
|
})
|
|
|
|
suite.Run("all", func() {
|
|
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
|
|
suite.Require().NoError(err)
|
|
suite.Require().ElementsMatch([]types.VaultResponse{
|
|
{
|
|
Denom: "usdx",
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: sdk.ZeroDec().String(),
|
|
TotalValue: sdk.ZeroInt(),
|
|
},
|
|
{
|
|
Denom: "busd",
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: sdk.ZeroDec().String(),
|
|
TotalValue: sdk.ZeroInt(),
|
|
},
|
|
},
|
|
res.Vaults,
|
|
)
|
|
})
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestVaults_WithSupply() {
|
|
vaultDenom := "usdx"
|
|
vault2Denom := testutil.TestBkavaDenoms[0]
|
|
|
|
depositAmount := sdk.NewInt64Coin(vaultDenom, 100)
|
|
deposit2Amount := sdk.NewInt64Coin(vault2Denom, 100)
|
|
|
|
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
|
|
acc := suite.CreateAccount(sdk.NewCoins(
|
|
sdk.NewInt64Coin(vaultDenom, 1000),
|
|
sdk.NewInt64Coin(vault2Denom, 1000),
|
|
), 0)
|
|
|
|
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_HARD)
|
|
suite.Require().NoError(err)
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), deposit2Amount, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Vaults, 2)
|
|
suite.Require().ElementsMatch(
|
|
[]types.VaultResponse{
|
|
{
|
|
Denom: vaultDenom,
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: depositAmount.Amount.ToDec().String(),
|
|
TotalValue: depositAmount.Amount,
|
|
},
|
|
{
|
|
Denom: vault2Denom,
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_SAVINGS},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: deposit2Amount.Amount.ToDec().String(),
|
|
TotalValue: deposit2Amount.Amount,
|
|
},
|
|
},
|
|
res.Vaults,
|
|
)
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestVaults_MixedSupply() {
|
|
vaultDenom := "usdx"
|
|
vault2Denom := "busd"
|
|
vault3Denom := testutil.TestBkavaDenoms[0]
|
|
|
|
depositAmount := sdk.NewInt64Coin(vault3Denom, 100)
|
|
|
|
suite.CreateVault(vaultDenom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
|
|
acc := suite.CreateAccount(sdk.NewCoins(
|
|
sdk.NewInt64Coin(vaultDenom, 1000),
|
|
sdk.NewInt64Coin(vault2Denom, 1000),
|
|
sdk.NewInt64Coin(vault3Denom, 1000),
|
|
), 0)
|
|
|
|
err := suite.Keeper.Deposit(suite.Ctx, acc.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
res, err := suite.queryClient.Vaults(context.Background(), types.NewQueryVaultsRequest())
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Vaults, 3)
|
|
suite.Require().ElementsMatch(
|
|
[]types.VaultResponse{
|
|
{
|
|
Denom: vaultDenom,
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: sdk.ZeroDec().String(),
|
|
TotalValue: sdk.ZeroInt(),
|
|
},
|
|
{
|
|
Denom: vault2Denom,
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_HARD},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: sdk.ZeroDec().String(),
|
|
TotalValue: sdk.ZeroInt(),
|
|
},
|
|
{
|
|
Denom: vault3Denom,
|
|
Strategies: []types.StrategyType{types.STRATEGY_TYPE_SAVINGS},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: nil,
|
|
TotalShares: depositAmount.Amount.ToDec().String(),
|
|
TotalValue: depositAmount.Amount,
|
|
},
|
|
},
|
|
res.Vaults,
|
|
)
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestVault_NotFound() {
|
|
_, err := suite.queryClient.Vault(context.Background(), types.NewQueryVaultRequest("usdx"))
|
|
suite.Require().Error(err)
|
|
suite.Require().ErrorIs(err, status.Errorf(codes.NotFound, "vault not found with specified denom"))
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestDeposits() {
|
|
// Validator setup for bkava
|
|
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
|
valAccAddr1, valAccAddr2, delegator := addrs[0], addrs[1], addrs[2]
|
|
valAddr1 := sdk.ValAddress(valAccAddr1)
|
|
valAddr2 := sdk.ValAddress(valAccAddr2)
|
|
|
|
vault1Denom := "usdx"
|
|
vault2Denom := "busd"
|
|
vault3Denom := fmt.Sprintf("bkava-%s", valAddr1.String())
|
|
vault4Denom := fmt.Sprintf("bkava-%s", valAddr2.String())
|
|
|
|
initialUkavaBalance := sdk.NewInt(1e9)
|
|
startBalance := sdk.NewCoins(
|
|
sdk.NewCoin("ukava", initialUkavaBalance),
|
|
sdk.NewInt64Coin(vault1Denom, 1000),
|
|
sdk.NewInt64Coin(vault2Denom, 1000),
|
|
// Bkava isn't actually minted via x/liquid
|
|
sdk.NewInt64Coin(vault3Denom, 1000),
|
|
sdk.NewInt64Coin(vault4Denom, 1000),
|
|
)
|
|
|
|
delegateAmount := sdk.NewInt(100e6)
|
|
|
|
suite.App.FundAccount(suite.Ctx, valAccAddr1, startBalance)
|
|
suite.App.FundAccount(suite.Ctx, valAccAddr2, startBalance)
|
|
suite.App.FundAccount(suite.Ctx, delegator, startBalance)
|
|
|
|
suite.CreateNewUnbondedValidator(valAddr1, initialUkavaBalance)
|
|
suite.CreateNewUnbondedValidator(valAddr2, initialUkavaBalance)
|
|
suite.CreateDelegation(valAddr1, delegator, delegateAmount)
|
|
suite.CreateDelegation(valAddr2, delegator, delegateAmount)
|
|
|
|
staking.EndBlocker(suite.Ctx, suite.App.GetStakingKeeper())
|
|
|
|
savingsParams := suite.SavingsKeeper.GetParams(suite.Ctx)
|
|
savingsParams.SupportedDenoms = append(savingsParams.SupportedDenoms, "bkava")
|
|
suite.SavingsKeeper.SetParams(suite.Ctx, savingsParams)
|
|
|
|
// Add vaults
|
|
suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
|
|
deposit1Amount := sdk.NewInt64Coin(vault1Denom, 100)
|
|
deposit2Amount := sdk.NewInt64Coin(vault2Denom, 200)
|
|
deposit3Amount := sdk.NewInt64Coin(vault3Denom, 200)
|
|
deposit4Amount := sdk.NewInt64Coin(vault4Denom, 300)
|
|
|
|
// Accounts
|
|
acc1 := suite.CreateAccount(startBalance, 0).GetAddress()
|
|
acc2 := delegator
|
|
|
|
// Deposit into each vault from each account - 4 total deposits
|
|
// Acc 1: usdx + busd
|
|
// Acc 2: usdx + bkava-1 + bkava-2
|
|
err := suite.Keeper.Deposit(suite.Ctx, acc1, deposit1Amount, types.STRATEGY_TYPE_HARD)
|
|
suite.Require().NoError(err)
|
|
err = suite.Keeper.Deposit(suite.Ctx, acc1, deposit2Amount, types.STRATEGY_TYPE_HARD)
|
|
suite.Require().NoError(err)
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, acc2, deposit1Amount, types.STRATEGY_TYPE_HARD)
|
|
suite.Require().NoError(err)
|
|
err = suite.Keeper.Deposit(suite.Ctx, acc2, deposit3Amount, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
err = suite.Keeper.Deposit(suite.Ctx, acc2, deposit4Amount, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Run("specific vault", func() {
|
|
// Query all deposits for account 1
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc1.String(), vault1Denom, false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().ElementsMatchf(
|
|
[]types.DepositResponse{
|
|
{
|
|
Depositor: acc1.String(),
|
|
// Only includes specified deposit shares
|
|
Shares: types.NewVaultShares(
|
|
types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()),
|
|
),
|
|
// Only the specified vault denom value
|
|
Value: sdk.NewCoins(deposit1Amount),
|
|
},
|
|
},
|
|
res.Deposits,
|
|
"deposits should match, got %v",
|
|
res.Deposits,
|
|
)
|
|
})
|
|
|
|
suite.Run("specific bkava vault", func() {
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc2.String(), vault3Denom, false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().ElementsMatchf(
|
|
[]types.DepositResponse{
|
|
{
|
|
Depositor: acc2.String(),
|
|
// Only includes specified deposit shares
|
|
Shares: types.NewVaultShares(
|
|
types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()),
|
|
),
|
|
// Only the specified vault denom value
|
|
Value: sdk.NewCoins(deposit3Amount),
|
|
},
|
|
},
|
|
res.Deposits,
|
|
"deposits should match, got %v",
|
|
res.Deposits,
|
|
)
|
|
})
|
|
|
|
suite.Run("specific bkava vault in staked tokens", func() {
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc2.String(), vault3Denom, true, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().Equal(
|
|
types.DepositResponse{
|
|
Depositor: acc2.String(),
|
|
// Only includes specified deposit shares
|
|
Shares: types.NewVaultShares(
|
|
types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()),
|
|
),
|
|
// Only the specified vault denom value
|
|
Value: sdk.NewCoins(
|
|
sdk.NewCoin("ukava", deposit3Amount.Amount),
|
|
),
|
|
},
|
|
res.Deposits[0],
|
|
)
|
|
})
|
|
|
|
suite.Run("invalid vault", func() {
|
|
_, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc1.String(), "notavaliddenom", false, nil),
|
|
)
|
|
suite.Require().Error(err)
|
|
suite.Require().ErrorIs(err, status.Errorf(codes.NotFound, "vault for notavaliddenom not found"))
|
|
})
|
|
|
|
suite.Run("all vaults", func() {
|
|
// Query all deposits for account 1
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc1.String(), "", false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().ElementsMatch(
|
|
[]types.DepositResponse{
|
|
{
|
|
Depositor: acc1.String(),
|
|
Shares: types.NewVaultShares(
|
|
types.NewVaultShare(deposit1Amount.Denom, deposit1Amount.Amount.ToDec()),
|
|
types.NewVaultShare(deposit2Amount.Denom, deposit2Amount.Amount.ToDec()),
|
|
),
|
|
Value: sdk.NewCoins(deposit1Amount, deposit2Amount),
|
|
},
|
|
},
|
|
res.Deposits,
|
|
)
|
|
})
|
|
|
|
suite.Run("all vaults value in staked tokens", func() {
|
|
// Query all deposits for account 1 with value in staked tokens
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc2.String(), "", true, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().Equal(
|
|
types.DepositResponse{
|
|
Depositor: acc2.String(),
|
|
Shares: types.VaultShares{
|
|
// Does not include non-bkava vaults
|
|
types.NewVaultShare(deposit4Amount.Denom, deposit4Amount.Amount.ToDec()),
|
|
types.NewVaultShare(deposit3Amount.Denom, deposit3Amount.Amount.ToDec()),
|
|
},
|
|
Value: sdk.Coins{
|
|
// Does not include non-bkava vaults
|
|
sdk.NewCoin("ukava", deposit4Amount.Amount),
|
|
sdk.NewCoin("ukava", deposit3Amount.Amount),
|
|
},
|
|
},
|
|
res.Deposits[0],
|
|
)
|
|
for i := range res.Deposits[0].Shares {
|
|
suite.Equal(
|
|
res.Deposits[0].Shares[i].Amount,
|
|
res.Deposits[0].Value[i].Amount.ToDec(),
|
|
"order of deposit value should match shares",
|
|
)
|
|
}
|
|
})
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestDeposits_NoDeposits() {
|
|
vault1Denom := "usdx"
|
|
vault2Denom := "busd"
|
|
|
|
// Add vaults
|
|
suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
|
|
// Accounts
|
|
acc1 := suite.CreateAccount(sdk.NewCoins(), 0).GetAddress()
|
|
|
|
suite.Run("specific vault", func() {
|
|
// Query all deposits for account 1
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc1.String(), vault1Denom, false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().ElementsMatchf(
|
|
[]types.DepositResponse{
|
|
{
|
|
Depositor: acc1.String(),
|
|
// Zero shares and zero value
|
|
Shares: nil,
|
|
Value: nil,
|
|
},
|
|
},
|
|
res.Deposits,
|
|
"deposits should match, got %v",
|
|
res.Deposits,
|
|
)
|
|
})
|
|
|
|
suite.Run("all vaults", func() {
|
|
// Query all deposits for account 1
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(acc1.String(), "", false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Empty(res.Deposits)
|
|
})
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestDeposits_NoDepositor() {
|
|
_, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest("", "usdx", false, nil),
|
|
)
|
|
suite.Require().Error(err)
|
|
suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "depositor is required"))
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestDeposits_InvalidAddress() {
|
|
_, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest("asdf", "usdx", false, nil),
|
|
)
|
|
suite.Require().Error(err)
|
|
suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "Invalid address"))
|
|
|
|
_, err = suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest("asdf", "", false, nil),
|
|
)
|
|
suite.Require().Error(err)
|
|
suite.Require().ErrorIs(err, status.Error(codes.InvalidArgument, "Invalid address"))
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestDeposits_bKava() {
|
|
// vault denom is only "bkava" which has it's own special handler
|
|
suite.CreateVault(
|
|
"bkava",
|
|
types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
|
|
false,
|
|
[]sdk.AccAddress{},
|
|
)
|
|
|
|
suite.CreateVault(
|
|
"ukava",
|
|
types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
|
|
false,
|
|
[]sdk.AccAddress{},
|
|
)
|
|
|
|
address1, derivatives1, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[0], sdk.NewInt(1e9))
|
|
address2, derivatives2, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[1], sdk.NewInt(1e9))
|
|
|
|
err := suite.App.FundAccount(suite.Ctx, address1, sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e9))))
|
|
suite.Require().NoError(err)
|
|
|
|
// Slash the last validator to reduce the value of it's derivatives to test bkava to underlying token conversion.
|
|
// First call end block to bond validator to enable slashing.
|
|
staking.EndBlocker(suite.Ctx, suite.App.GetStakingKeeper())
|
|
err = suite.slashValidator(sdk.ValAddress(address2), sdk.MustNewDecFromStr("0.5"))
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Run("no deposits", func() {
|
|
// Query all deposits for account 1
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(address1.String(), "bkava", false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
suite.Require().ElementsMatchf(
|
|
[]types.DepositResponse{
|
|
{
|
|
Depositor: address1.String(),
|
|
// Zero shares for "bkava" aggregate
|
|
Shares: nil,
|
|
// Only the specified vault denom value
|
|
Value: nil,
|
|
},
|
|
},
|
|
res.Deposits,
|
|
"deposits should match, got %v",
|
|
res.Deposits,
|
|
)
|
|
})
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, address1, derivatives1, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
err = suite.BankKeeper.SendCoins(suite.Ctx, address2, address1, sdk.NewCoins(derivatives2))
|
|
suite.Require().NoError(err)
|
|
err = suite.Keeper.Deposit(suite.Ctx, address1, derivatives2, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, address1, sdk.NewInt64Coin("ukava", 1e6), types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Run("multiple deposits", func() {
|
|
// Query all deposits for account 1
|
|
res, err := suite.queryClient.Deposits(
|
|
context.Background(),
|
|
types.NewQueryDepositsRequest(address1.String(), "bkava", false, nil),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Len(res.Deposits, 1)
|
|
// first validator isn't slashed, so bkava units equal to underlying staked tokens
|
|
// last validator slashed 50% so derivatives are worth half
|
|
// Excludes non-bkava deposits
|
|
expectedValue := derivatives1.Amount.Add(derivatives2.Amount.QuoRaw(2))
|
|
suite.Require().ElementsMatchf(
|
|
[]types.DepositResponse{
|
|
{
|
|
Depositor: address1.String(),
|
|
// Zero shares for "bkava" aggregate
|
|
Shares: nil,
|
|
// Value returned in units of staked token
|
|
Value: sdk.NewCoins(
|
|
sdk.NewCoin(suite.bondDenom(), expectedValue),
|
|
),
|
|
},
|
|
},
|
|
res.Deposits,
|
|
"deposits should match, got %v",
|
|
res.Deposits,
|
|
)
|
|
})
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestVault_bKava_Single() {
|
|
vaultDenom := "bkava"
|
|
coinDenom := testutil.TestBkavaDenoms[0]
|
|
|
|
startBalance := sdk.NewInt64Coin(coinDenom, 1000)
|
|
depositAmount := sdk.NewInt64Coin(coinDenom, 100)
|
|
|
|
acc1 := suite.CreateAccount(sdk.NewCoins(startBalance), 0)
|
|
|
|
// vault denom is only "bkava" which has it's own special handler
|
|
suite.CreateVault(
|
|
vaultDenom,
|
|
types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
|
|
false,
|
|
[]sdk.AccAddress{},
|
|
)
|
|
|
|
err := suite.Keeper.Deposit(suite.Ctx, acc1.GetAddress(), depositAmount, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(
|
|
err,
|
|
"should be able to deposit bkava derivative denom in bkava vault",
|
|
)
|
|
|
|
res, err := suite.queryClient.Vault(
|
|
context.Background(),
|
|
types.NewQueryVaultRequest(coinDenom),
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Equal(
|
|
types.VaultResponse{
|
|
Denom: coinDenom,
|
|
Strategies: types.StrategyTypes{
|
|
types.STRATEGY_TYPE_SAVINGS,
|
|
},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: []string(nil),
|
|
TotalShares: "100.000000000000000000",
|
|
TotalValue: sdk.NewInt(100),
|
|
},
|
|
res.Vault,
|
|
)
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestVault_bKava_Aggregate() {
|
|
vaultDenom := "bkava"
|
|
|
|
address1, derivatives1, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[0], sdk.NewInt(1e9))
|
|
address2, derivatives2, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[1], sdk.NewInt(1e9))
|
|
address3, derivatives3, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[2], sdk.NewInt(1e9))
|
|
// Slash the last validator to reduce the value of it's derivatives to test bkava to underlying token conversion.
|
|
// First call end block to bond validator to enable slashing.
|
|
staking.EndBlocker(suite.Ctx, suite.App.GetStakingKeeper())
|
|
err := suite.slashValidator(sdk.ValAddress(address3), sdk.MustNewDecFromStr("0.5"))
|
|
suite.Require().NoError(err)
|
|
|
|
// vault denom is only "bkava" which has it's own special handler
|
|
suite.CreateVault(
|
|
vaultDenom,
|
|
types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
|
|
false,
|
|
[]sdk.AccAddress{},
|
|
)
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, address1, derivatives1, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, address2, derivatives2, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
err = suite.Keeper.Deposit(suite.Ctx, address3, derivatives3, types.STRATEGY_TYPE_SAVINGS)
|
|
suite.Require().NoError(err)
|
|
|
|
// Query "bkava" to get aggregate amount
|
|
res, err := suite.queryClient.Vault(
|
|
context.Background(),
|
|
types.NewQueryVaultRequest(vaultDenom),
|
|
)
|
|
suite.Require().NoError(err)
|
|
// first two validators are not slashed, so bkava units equal to underlying staked tokens
|
|
expectedValue := derivatives1.Amount.Add(derivatives2.Amount)
|
|
// last validator slashed 50% so derivatives are worth half
|
|
expectedValue = expectedValue.Add(derivatives2.Amount.QuoRaw(2))
|
|
suite.Require().Equal(
|
|
types.VaultResponse{
|
|
Denom: vaultDenom,
|
|
Strategies: types.StrategyTypes{
|
|
types.STRATEGY_TYPE_SAVINGS,
|
|
},
|
|
IsPrivateVault: false,
|
|
AllowedDepositors: []string(nil),
|
|
// No shares for aggregate
|
|
TotalShares: "0",
|
|
TotalValue: expectedValue,
|
|
},
|
|
res.Vault,
|
|
)
|
|
}
|
|
|
|
func (suite *grpcQueryTestSuite) TestTotalSupply() {
|
|
deposit := func(addr sdk.AccAddress, denom string, amount int64) {
|
|
err := suite.Keeper.Deposit(
|
|
suite.Ctx,
|
|
addr,
|
|
sdk.NewInt64Coin(denom, amount),
|
|
types.STRATEGY_TYPE_SAVINGS,
|
|
)
|
|
suite.Require().NoError(err)
|
|
}
|
|
testCases := []struct {
|
|
name string
|
|
setup func()
|
|
expectedSupply sdk.Coins
|
|
}{
|
|
{
|
|
name: "no vaults mean no supply",
|
|
setup: func() {},
|
|
expectedSupply: nil,
|
|
},
|
|
{
|
|
name: "no savings vaults mean no supply",
|
|
setup: func() {
|
|
suite.CreateVault("usdx", types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
suite.CreateVault("busd", types.StrategyTypes{types.STRATEGY_TYPE_HARD}, false, nil)
|
|
},
|
|
expectedSupply: nil,
|
|
},
|
|
{
|
|
name: "empty savings vaults mean no supply",
|
|
setup: func() {
|
|
suite.CreateVault("usdx", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
suite.CreateVault("busd", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
},
|
|
expectedSupply: nil,
|
|
},
|
|
{
|
|
name: "calculates supply of savings vaults",
|
|
setup: func() {
|
|
vault1Denom := "usdx"
|
|
vault2Denom := "busd"
|
|
suite.CreateVault(vault1Denom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
suite.CreateVault(vault2Denom, types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, nil)
|
|
|
|
acc1 := suite.CreateAccount(sdk.NewCoins(
|
|
sdk.NewInt64Coin(vault1Denom, 1e6),
|
|
sdk.NewInt64Coin(vault2Denom, 1e6),
|
|
), 0)
|
|
deposit(acc1.GetAddress(), vault1Denom, 1e5)
|
|
deposit(acc1.GetAddress(), vault2Denom, 1e5)
|
|
acc2 := suite.CreateAccount(sdk.NewCoins(
|
|
sdk.NewInt64Coin(vault1Denom, 1e6),
|
|
sdk.NewInt64Coin(vault2Denom, 1e6),
|
|
), 0)
|
|
deposit(acc2.GetAddress(), vault1Denom, 2e5)
|
|
deposit(acc2.GetAddress(), vault2Denom, 2e5)
|
|
},
|
|
expectedSupply: sdk.NewCoins(
|
|
sdk.NewInt64Coin("usdx", 3e5),
|
|
sdk.NewInt64Coin("busd", 3e5),
|
|
),
|
|
},
|
|
{
|
|
name: "calculates supply of savings vaults, even when private",
|
|
setup: func() {
|
|
vault1Denom := "ukava"
|
|
vault2Denom := "busd"
|
|
|
|
acc1 := suite.CreateAccount(sdk.NewCoins(
|
|
sdk.NewInt64Coin(vault1Denom, 1e6),
|
|
sdk.NewInt64Coin(vault2Denom, 1e6),
|
|
), 0)
|
|
acc2 := suite.CreateAccount(sdk.NewCoins(
|
|
sdk.NewInt64Coin(vault1Denom, 1e6),
|
|
sdk.NewInt64Coin(vault2Denom, 1e6),
|
|
), 0)
|
|
|
|
suite.CreateVault(
|
|
vault1Denom,
|
|
types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
|
|
true, // private!
|
|
[]sdk.AccAddress{acc1.GetAddress()}, // only acc1 can deposit.
|
|
)
|
|
suite.CreateVault("busd",
|
|
types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS},
|
|
false,
|
|
nil,
|
|
)
|
|
|
|
deposit(acc1.GetAddress(), vault1Denom, 1e5)
|
|
deposit(acc1.GetAddress(), vault2Denom, 1e5)
|
|
deposit(acc2.GetAddress(), vault2Denom, 2e5)
|
|
},
|
|
expectedSupply: sdk.NewCoins(
|
|
sdk.NewInt64Coin("ukava", 1e5),
|
|
sdk.NewInt64Coin("busd", 3e5),
|
|
),
|
|
},
|
|
{
|
|
name: "aggregates supply of bkava vaults accounting for slashing",
|
|
setup: func() {
|
|
address1, derivatives1, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[0], sdk.NewInt(1e9))
|
|
address2, derivatives2, _ := suite.createAccountWithDerivatives(testutil.TestBkavaDenoms[1], sdk.NewInt(1e9))
|
|
|
|
// bond validators
|
|
staking.EndBlocker(suite.Ctx, suite.App.GetStakingKeeper())
|
|
// slash val2 - its shares are now 80% as valuable!
|
|
err := suite.slashValidator(sdk.ValAddress(address2), sdk.MustNewDecFromStr("0.2"))
|
|
suite.Require().NoError(err)
|
|
|
|
// create "bkava" vault. it holds all bkava denoms
|
|
suite.CreateVault("bkava", types.StrategyTypes{types.STRATEGY_TYPE_SAVINGS}, false, []sdk.AccAddress{})
|
|
|
|
// deposit bkava
|
|
deposit(address1, testutil.TestBkavaDenoms[0], derivatives1.Amount.Int64())
|
|
deposit(address2, testutil.TestBkavaDenoms[1], derivatives2.Amount.Int64())
|
|
},
|
|
expectedSupply: sdk.NewCoins(
|
|
sdk.NewCoin(
|
|
"bkava",
|
|
sdk.NewIntFromUint64(1e9). // derivative 1
|
|
Add(sdk.NewInt(1e9).MulRaw(80).QuoRaw(100))), // derivative 2: original value * 80%
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
suite.Run(tc.name, func() {
|
|
suite.SetupTest()
|
|
tc.setup()
|
|
res, err := suite.queryClient.TotalSupply(
|
|
sdk.WrapSDKContext(suite.Ctx),
|
|
&types.QueryTotalSupplyRequest{},
|
|
)
|
|
suite.Require().NoError(err)
|
|
suite.Require().Equal(tc.expectedSupply, res.Result)
|
|
})
|
|
}
|
|
}
|
|
|
|
// createUnbondedValidator creates an unbonded validator with the given amount of self-delegation.
|
|
func (suite *grpcQueryTestSuite) createUnbondedValidator(address sdk.ValAddress, selfDelegation sdk.Coin, minSelfDelegation sdk.Int) error {
|
|
msg, err := stakingtypes.NewMsgCreateValidator(
|
|
address,
|
|
ed25519.GenPrivKey().PubKey(),
|
|
selfDelegation,
|
|
stakingtypes.Description{},
|
|
stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
|
minSelfDelegation,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
msgServer := stakingkeeper.NewMsgServerImpl(suite.App.GetStakingKeeper())
|
|
_, err = msgServer.CreateValidator(sdk.WrapSDKContext(suite.Ctx), msg)
|
|
return err
|
|
}
|
|
|
|
// createAccountWithDerivatives creates an account with the given amount and denom of derivative token.
|
|
// Internally, it creates a validator account and mints derivatives from the validator's self delegation.
|
|
func (suite *grpcQueryTestSuite) createAccountWithDerivatives(denom string, amount sdk.Int) (sdk.AccAddress, sdk.Coin, sdk.Coins) {
|
|
valAddress, err := liquidtypes.ParseLiquidStakingTokenDenom(denom)
|
|
suite.Require().NoError(err)
|
|
address := sdk.AccAddress(valAddress)
|
|
|
|
remainingSelfDelegation := sdk.NewInt(1e6)
|
|
selfDelegation := sdk.NewCoin(
|
|
suite.bondDenom(),
|
|
amount.Add(remainingSelfDelegation),
|
|
)
|
|
|
|
suite.NewAccountFromAddr(address, sdk.NewCoins(selfDelegation))
|
|
|
|
err = suite.createUnbondedValidator(valAddress, selfDelegation, remainingSelfDelegation)
|
|
suite.Require().NoError(err)
|
|
|
|
toConvert := sdk.NewCoin(suite.bondDenom(), amount)
|
|
derivatives, err := suite.App.GetLiquidKeeper().MintDerivative(suite.Ctx,
|
|
address,
|
|
valAddress,
|
|
toConvert,
|
|
)
|
|
suite.Require().NoError(err)
|
|
|
|
fullBalance := suite.BankKeeper.GetAllBalances(suite.Ctx, address)
|
|
|
|
return address, derivatives, fullBalance
|
|
}
|
|
|
|
// slashValidator slashes the validator with the given address by the given percentage.
|
|
func (suite *grpcQueryTestSuite) slashValidator(address sdk.ValAddress, slashFraction sdk.Dec) error {
|
|
stakingKeeper := suite.App.GetStakingKeeper()
|
|
|
|
validator, found := stakingKeeper.GetValidator(suite.Ctx, address)
|
|
suite.Require().True(found)
|
|
consAddr, err := validator.GetConsAddr()
|
|
suite.Require().NoError(err)
|
|
|
|
// Assume infraction was at current height. Note unbonding delegations and redelegations are only slashed if created after
|
|
// the infraction height so none will be slashed.
|
|
infractionHeight := suite.Ctx.BlockHeight()
|
|
|
|
power := stakingKeeper.TokensToConsensusPower(suite.Ctx, validator.GetTokens())
|
|
|
|
stakingKeeper.Slash(suite.Ctx, consAddr, infractionHeight, power, slashFraction)
|
|
return nil
|
|
}
|
|
|
|
// bondDenom fetches the staking denom from the staking module.
|
|
func (suite *grpcQueryTestSuite) bondDenom() string {
|
|
return suite.App.GetStakingKeeper().BondDenom(suite.Ctx)
|
|
}
|