2022-03-24 16:43:03 +00:00
|
|
|
package keeper_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
|
2023-04-05 23:21:59 +00:00
|
|
|
sdkmath "cosmossdk.io/math"
|
2022-11-08 17:43:26 +00:00
|
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
|
2022-03-24 16:43:03 +00:00
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
tmprototypes "github.com/tendermint/tendermint/proto/tendermint/types"
|
|
|
|
|
2022-11-08 17:43:26 +00:00
|
|
|
"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"
|
2022-03-24 16:43:03 +00:00
|
|
|
"github.com/kava-labs/kava/app"
|
2022-11-08 17:43:26 +00:00
|
|
|
liquidtypes "github.com/kava-labs/kava/x/liquid/types"
|
2022-03-24 16:43:03 +00:00
|
|
|
"github.com/kava-labs/kava/x/savings/keeper"
|
|
|
|
"github.com/kava-labs/kava/x/savings/types"
|
|
|
|
)
|
|
|
|
|
2022-11-08 17:43:26 +00:00
|
|
|
var dep = types.NewDeposit
|
|
|
|
|
|
|
|
const (
|
|
|
|
bkava1 = "bkava-kavavaloper15gqc744d05xacn4n6w2furuads9fu4pqn6zxlu"
|
|
|
|
bkava2 = "bkava-kavavaloper15qdefkmwswysgg4qxgqpqr35k3m49pkx8yhpte"
|
|
|
|
)
|
|
|
|
|
2022-03-24 16:43:03 +00:00
|
|
|
type grpcQueryTestSuite struct {
|
|
|
|
suite.Suite
|
|
|
|
|
|
|
|
tApp app.TestApp
|
|
|
|
ctx sdk.Context
|
|
|
|
keeper keeper.Keeper
|
|
|
|
queryServer types.QueryServer
|
|
|
|
addrs []sdk.AccAddress
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *grpcQueryTestSuite) SetupTest() {
|
|
|
|
suite.tApp = app.NewTestApp()
|
|
|
|
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
|
|
|
|
|
|
|
suite.addrs = addrs
|
|
|
|
|
|
|
|
suite.ctx = suite.tApp.NewContext(true, tmprototypes.Header{}).
|
|
|
|
WithBlockTime(time.Now().UTC())
|
|
|
|
suite.keeper = suite.tApp.GetSavingsKeeper()
|
|
|
|
suite.queryServer = keeper.NewQueryServerImpl(suite.keeper)
|
|
|
|
|
|
|
|
err := suite.tApp.FundModuleAccount(
|
|
|
|
suite.ctx,
|
|
|
|
types.ModuleAccountName,
|
|
|
|
cs(
|
|
|
|
c("usdx", 10000000000),
|
|
|
|
c("busd", 10000000000),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
savingsGenesis := types.GenesisState{
|
2022-11-08 17:43:26 +00:00
|
|
|
Params: types.NewParams([]string{"bnb", "busd", bkava1, bkava2}),
|
2022-03-24 16:43:03 +00:00
|
|
|
}
|
|
|
|
savingsGenState := app.GenesisState{types.ModuleName: suite.tApp.AppCodec().MustMarshalJSON(&savingsGenesis)}
|
|
|
|
|
|
|
|
suite.tApp.InitializeFromGenesisStates(
|
|
|
|
savingsGenState,
|
|
|
|
app.NewFundedGenStateWithSameCoins(
|
|
|
|
suite.tApp.AppCodec(),
|
|
|
|
cs(
|
|
|
|
c("bnb", 10000000000),
|
|
|
|
c("busd", 20000000000),
|
|
|
|
),
|
|
|
|
addrs,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *grpcQueryTestSuite) TestGrpcQueryParams() {
|
|
|
|
res, err := suite.queryServer.Params(sdk.WrapSDKContext(suite.ctx), &types.QueryParamsRequest{})
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
var expected types.GenesisState
|
|
|
|
savingsGenesis := types.GenesisState{
|
2022-11-08 17:43:26 +00:00
|
|
|
Params: types.NewParams([]string{"bnb", "busd", bkava1, bkava2}),
|
2022-03-24 16:43:03 +00:00
|
|
|
}
|
|
|
|
savingsGenState := app.GenesisState{types.ModuleName: suite.tApp.AppCodec().MustMarshalJSON(&savingsGenesis)}
|
|
|
|
suite.tApp.AppCodec().MustUnmarshalJSON(savingsGenState[types.ModuleName], &expected)
|
|
|
|
|
|
|
|
suite.Equal(expected.Params, res.Params, "params should equal test genesis state")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *grpcQueryTestSuite) TestGrpcQueryDeposits() {
|
2022-11-08 17:43:26 +00:00
|
|
|
suite.addDeposits([]types.Deposit{
|
|
|
|
dep(suite.addrs[0], cs(c("bnb", 100000000))),
|
|
|
|
dep(suite.addrs[1], cs(c("bnb", 20000000))),
|
|
|
|
dep(suite.addrs[0], cs(c("busd", 20000000))),
|
|
|
|
dep(suite.addrs[0], cs(c("busd", 8000000))),
|
|
|
|
})
|
2022-03-24 16:43:03 +00:00
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
giveName string
|
|
|
|
giveRequest *types.QueryDepositsRequest
|
|
|
|
wantDepositCounts int
|
|
|
|
shouldError bool
|
|
|
|
errorSubstr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"empty query",
|
|
|
|
&types.QueryDepositsRequest{},
|
|
|
|
2,
|
|
|
|
false,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"owner",
|
|
|
|
&types.QueryDepositsRequest{
|
|
|
|
Owner: suite.addrs[0].String(),
|
|
|
|
},
|
|
|
|
// Excludes the second address
|
|
|
|
1,
|
|
|
|
false,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"invalid owner",
|
|
|
|
&types.QueryDepositsRequest{
|
|
|
|
Owner: "invalid address",
|
|
|
|
},
|
|
|
|
// No deposits
|
|
|
|
0,
|
|
|
|
true,
|
|
|
|
"decoding bech32 failed",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"owner and denom",
|
|
|
|
&types.QueryDepositsRequest{
|
|
|
|
Owner: suite.addrs[0].String(),
|
|
|
|
Denom: "bnb",
|
|
|
|
},
|
|
|
|
// Only the first one
|
|
|
|
1,
|
|
|
|
false,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"owner and invalid denom empty response",
|
|
|
|
&types.QueryDepositsRequest{
|
|
|
|
Owner: suite.addrs[0].String(),
|
|
|
|
Denom: "invalid denom",
|
|
|
|
},
|
|
|
|
0,
|
|
|
|
false,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"denom",
|
|
|
|
&types.QueryDepositsRequest{
|
|
|
|
Denom: "bnb",
|
|
|
|
},
|
|
|
|
2,
|
|
|
|
false,
|
|
|
|
"",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
suite.Run(tt.giveName, func() {
|
|
|
|
res, err := suite.queryServer.Deposits(sdk.WrapSDKContext(suite.ctx), tt.giveRequest)
|
|
|
|
|
|
|
|
if tt.shouldError {
|
|
|
|
suite.Error(err)
|
|
|
|
suite.Contains(err.Error(), tt.errorSubstr)
|
|
|
|
} else {
|
|
|
|
suite.NoError(err)
|
|
|
|
suite.Equal(tt.wantDepositCounts, len(res.Deposits))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:43:26 +00:00
|
|
|
func (suite *grpcQueryTestSuite) TestGrpcQueryTotalSupply() {
|
|
|
|
testCases := []struct {
|
|
|
|
name string
|
|
|
|
deposits types.Deposits
|
|
|
|
expectedSupply sdk.Coins
|
2022-03-24 16:43:03 +00:00
|
|
|
}{
|
|
|
|
{
|
2022-11-08 17:43:26 +00:00
|
|
|
name: "returns zeros when there's no supply",
|
|
|
|
deposits: []types.Deposit{},
|
|
|
|
expectedSupply: sdk.NewCoins(),
|
2022-03-24 16:43:03 +00:00
|
|
|
},
|
|
|
|
{
|
2022-11-08 17:43:26 +00:00
|
|
|
name: "returns supply of one denom deposited from multiple accounts",
|
|
|
|
deposits: []types.Deposit{
|
|
|
|
dep(suite.addrs[0], sdk.NewCoins(c("busd", 1e6))),
|
|
|
|
dep(suite.addrs[1], sdk.NewCoins(c("busd", 1e6))),
|
|
|
|
},
|
|
|
|
expectedSupply: sdk.NewCoins(c("busd", 2e6)),
|
2022-03-24 16:43:03 +00:00
|
|
|
},
|
|
|
|
{
|
2022-11-08 17:43:26 +00:00
|
|
|
name: "returns supply of multiple denoms deposited from single account",
|
|
|
|
deposits: []types.Deposit{
|
|
|
|
dep(suite.addrs[0], sdk.NewCoins(c("busd", 1e6), c("bnb", 1e6))),
|
|
|
|
},
|
|
|
|
expectedSupply: sdk.NewCoins(c("busd", 1e6), c("bnb", 1e6)),
|
2022-03-24 16:43:03 +00:00
|
|
|
},
|
|
|
|
{
|
2022-11-08 17:43:26 +00:00
|
|
|
name: "returns supply of multiple denoms deposited from multiple accounts",
|
|
|
|
deposits: []types.Deposit{
|
|
|
|
dep(suite.addrs[0], sdk.NewCoins(c("busd", 1e6), c("bnb", 1e6))),
|
|
|
|
dep(suite.addrs[1], sdk.NewCoins(c("busd", 1e6), c("bnb", 1e6))),
|
|
|
|
},
|
|
|
|
expectedSupply: sdk.NewCoins(c("busd", 2e6), c("bnb", 2e6)),
|
2022-03-24 16:43:03 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:43:26 +00:00
|
|
|
for _, tc := range testCases {
|
|
|
|
suite.Run(tc.name, func() {
|
|
|
|
suite.SetupTest()
|
|
|
|
// setup deposits
|
|
|
|
suite.addDeposits(tc.deposits)
|
|
|
|
|
|
|
|
res, err := suite.queryServer.TotalSupply(
|
|
|
|
sdk.WrapSDKContext(suite.ctx),
|
|
|
|
&types.QueryTotalSupplyRequest{},
|
|
|
|
)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(tc.expectedSupply, res.Result)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
suite.Run("aggregates bkava denoms, accounting for slashing", func() {
|
|
|
|
suite.SetupTest()
|
|
|
|
|
2023-04-05 23:21:59 +00:00
|
|
|
address1, derivatives1, _ := suite.createAccountWithDerivatives(bkava1, sdkmath.NewInt(1e9))
|
|
|
|
address2, derivatives2, _ := suite.createAccountWithDerivatives(bkava2, sdkmath.NewInt(1e9))
|
2022-11-08 17:43:26 +00:00
|
|
|
|
|
|
|
// bond validators
|
|
|
|
staking.EndBlocker(suite.ctx, suite.tApp.GetStakingKeeper())
|
|
|
|
// slash val2 - its shares are now 80% as valuable!
|
|
|
|
err := suite.slashValidator(sdk.ValAddress(address2), sdk.MustNewDecFromStr("0.2"))
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
suite.addDeposits(
|
|
|
|
types.Deposits{
|
|
|
|
dep(address1, cs(derivatives1)),
|
|
|
|
dep(address2, cs(derivatives2)),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
expectedSupply := sdk.NewCoins(
|
|
|
|
sdk.NewCoin(
|
|
|
|
"bkava",
|
2023-04-05 23:21:59 +00:00
|
|
|
sdkmath.NewIntFromUint64(1e9). // derivative 1
|
|
|
|
Add(sdkmath.NewInt(1e9).MulRaw(80).QuoRaw(100))), // derivative 2: original value * 80%
|
2022-11-08 17:43:26 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
res, err := suite.queryServer.TotalSupply(
|
|
|
|
sdk.WrapSDKContext(suite.ctx),
|
|
|
|
&types.QueryTotalSupplyRequest{},
|
|
|
|
)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
suite.Require().Equal(expectedSupply, res.Result)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (suite *grpcQueryTestSuite) addDeposits(deposits types.Deposits) {
|
2022-03-24 16:43:03 +00:00
|
|
|
for _, dep := range deposits {
|
|
|
|
suite.NotPanics(func() {
|
2022-11-08 17:43:26 +00:00
|
|
|
err := suite.keeper.Deposit(suite.ctx, dep.Depositor, dep.Amount)
|
2022-03-24 16:43:03 +00:00
|
|
|
suite.Require().NoError(err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-08 17:43:26 +00:00
|
|
|
// createUnbondedValidator creates an unbonded validator with the given amount of self-delegation.
|
2023-04-05 23:21:59 +00:00
|
|
|
func (suite *grpcQueryTestSuite) createUnbondedValidator(address sdk.ValAddress, selfDelegation sdk.Coin, minSelfDelegation sdkmath.Int) error {
|
2022-11-08 17:43:26 +00:00
|
|
|
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.tApp.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.
|
2023-04-05 23:21:59 +00:00
|
|
|
func (suite *grpcQueryTestSuite) createAccountWithDerivatives(denom string, amount sdkmath.Int) (sdk.AccAddress, sdk.Coin, sdk.Coins) {
|
2022-11-08 17:43:26 +00:00
|
|
|
bondDenom := suite.tApp.GetStakingKeeper().BondDenom(suite.ctx)
|
|
|
|
valAddress, err := liquidtypes.ParseLiquidStakingTokenDenom(denom)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
address := sdk.AccAddress(valAddress)
|
|
|
|
|
2023-04-05 23:21:59 +00:00
|
|
|
remainingSelfDelegation := sdkmath.NewInt(1e6)
|
2022-11-08 17:43:26 +00:00
|
|
|
selfDelegation := sdk.NewCoin(
|
|
|
|
bondDenom,
|
|
|
|
amount.Add(remainingSelfDelegation),
|
|
|
|
)
|
|
|
|
|
|
|
|
// create & fund account
|
|
|
|
// ak := suite.tApp.GetAccountKeeper()
|
|
|
|
// acc := ak.NewAccountWithAddress(suite.ctx, address)
|
|
|
|
// ak.SetAccount(suite.ctx, acc)
|
|
|
|
err = suite.tApp.FundAccount(suite.ctx, address, sdk.NewCoins(selfDelegation))
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
err = suite.createUnbondedValidator(valAddress, selfDelegation, remainingSelfDelegation)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
toConvert := sdk.NewCoin(bondDenom, amount)
|
|
|
|
derivatives, err := suite.tApp.GetLiquidKeeper().MintDerivative(suite.ctx,
|
|
|
|
address,
|
|
|
|
valAddress,
|
|
|
|
toConvert,
|
|
|
|
)
|
|
|
|
suite.Require().NoError(err)
|
|
|
|
|
|
|
|
fullBalance := suite.tApp.GetBankKeeper().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.tApp.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
|
|
|
|
}
|
|
|
|
|
2022-03-24 16:43:03 +00:00
|
|
|
func TestGrpcQueryTestSuite(t *testing.T) {
|
|
|
|
suite.Run(t, new(grpcQueryTestSuite))
|
|
|
|
}
|