-
Notifications
You must be signed in to change notification settings - Fork 367
/
suite.go
365 lines (304 loc) · 13.7 KB
/
suite.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package testutil
import (
"fmt"
"reflect"
sdkmath "cosmossdk.io/math"
abci "github.com/cometbft/cometbft/abci/types"
tmproto "github.com/cometbft/cometbft/proto/tendermint/types"
tmtime "github.com/cometbft/cometbft/types/time"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/stretchr/testify/suite"
"github.com/kava-labs/kava/app"
earnkeeper "github.com/kava-labs/kava/x/earn/keeper"
earntypes "github.com/kava-labs/kava/x/earn/types"
"github.com/kava-labs/kava/x/router/keeper"
savingstypes "github.com/kava-labs/kava/x/savings/types"
)
// Test suite used for all keeper tests
type Suite struct {
suite.Suite
App app.TestApp
Ctx sdk.Context
Keeper keeper.Keeper
BankKeeper bankkeeper.Keeper
StakingKeeper *stakingkeeper.Keeper
EarnKeeper earnkeeper.Keeper
}
// The default state used by each test
func (suite *Suite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
tApp.InitializeFromGenesisStates()
suite.App = tApp
suite.Ctx = ctx
suite.Keeper = tApp.GetRouterKeeper()
suite.StakingKeeper = tApp.GetStakingKeeper()
suite.BankKeeper = tApp.GetBankKeeper()
suite.EarnKeeper = tApp.GetEarnKeeper()
}
// CreateAccount creates a new account from the provided balance and address
func (suite *Suite) CreateAccountWithAddress(addr sdk.AccAddress, initialBalance sdk.Coins) authtypes.AccountI {
ak := suite.App.GetAccountKeeper()
acc := ak.NewAccountWithAddress(suite.Ctx, addr)
ak.SetAccount(suite.Ctx, acc)
err := suite.App.FundAccount(suite.Ctx, acc.GetAddress(), initialBalance)
suite.Require().NoError(err)
return acc
}
// CreateVestingAccount creates a new vesting account. `vestingBalance` should be a fraction of `initialBalance`.
func (suite *Suite) CreateVestingAccountWithAddress(addr sdk.AccAddress, initialBalance sdk.Coins, vestingBalance sdk.Coins) authtypes.AccountI {
if vestingBalance.IsAnyGT(initialBalance) {
panic("vesting balance must be less than initial balance")
}
acc := suite.CreateAccountWithAddress(addr, initialBalance)
bacc := acc.(*authtypes.BaseAccount)
periods := vestingtypes.Periods{
vestingtypes.Period{
Length: 31556952,
Amount: vestingBalance,
},
}
vacc := vestingtypes.NewPeriodicVestingAccount(bacc, vestingBalance, suite.Ctx.BlockTime().Unix(), periods)
suite.App.GetAccountKeeper().SetAccount(suite.Ctx, vacc)
return vacc
}
// AddCoinsToModule adds coins to the a module account, creating it if it doesn't exist.
func (suite *Suite) AddCoinsToModule(module string, amount sdk.Coins) {
err := suite.App.FundModuleAccount(suite.Ctx, module, amount)
suite.Require().NoError(err)
}
// AccountBalanceEqual checks if an account has the specified coins.
func (suite *Suite) AccountBalanceEqual(addr sdk.AccAddress, coins sdk.Coins) {
balance := suite.BankKeeper.GetAllBalances(suite.Ctx, addr)
suite.Equalf(coins, balance, "expected account balance to equal coins %s, but got %s", coins, balance)
}
// AccountBalanceOfEqual checks if an account has the specified amount of one denom.
func (suite *Suite) AccountBalanceOfEqual(addr sdk.AccAddress, denom string, amount sdkmath.Int) {
balance := suite.BankKeeper.GetBalance(suite.Ctx, addr, denom).Amount
suite.Equalf(amount, balance, "expected account balance to have %[1]s%[2]s, but got %[3]s%[2]s", amount, denom, balance)
}
// AccountSpendableBalanceEqual checks if an account has the specified coins unlocked.
func (suite *Suite) AccountSpendableBalanceEqual(addr sdk.AccAddress, amount sdk.Coins) {
balance := suite.BankKeeper.SpendableCoins(suite.Ctx, addr)
expectedAmt := amount
if expectedAmt == nil {
expectedAmt = sdk.NewCoins()
}
suite.Equalf(expectedAmt, balance, "expected account spendable balance to equal coins %s, but got %s", amount, balance)
}
func (suite *Suite) QueryBank_SpendableBalance(user sdk.AccAddress) sdk.Coins {
res, err := suite.BankKeeper.SpendableBalances(
sdk.WrapSDKContext(suite.Ctx),
&banktypes.QuerySpendableBalancesRequest{
Address: user.String(),
},
)
suite.Require().NoError(err)
return *&res.Balances
}
func (suite *Suite) deliverMsgCreateValidator(ctx sdk.Context, address sdk.ValAddress, selfDelegation sdk.Coin) error {
msg, err := stakingtypes.NewMsgCreateValidator(
address,
ed25519.GenPrivKey().PubKey(),
selfDelegation,
stakingtypes.Description{},
stakingtypes.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
sdkmath.NewInt(1e6),
)
if err != nil {
return err
}
msgServer := stakingkeeper.NewMsgServerImpl(suite.StakingKeeper)
_, err = msgServer.CreateValidator(sdk.WrapSDKContext(suite.Ctx), msg)
return err
}
// NewBondCoin creates a Coin with the current staking denom.
func (suite *Suite) NewBondCoin(amount sdkmath.Int) sdk.Coin {
stakingDenom := suite.StakingKeeper.BondDenom(suite.Ctx)
return sdk.NewCoin(stakingDenom, amount)
}
// NewBondCoins creates Coins with the current staking denom.
func (suite *Suite) NewBondCoins(amount sdkmath.Int) sdk.Coins {
return sdk.NewCoins(suite.NewBondCoin(amount))
}
// CreateNewUnbondedValidator creates a new validator in the staking module.
// New validators are unbonded until the end blocker is run.
func (suite *Suite) CreateNewUnbondedValidator(addr sdk.ValAddress, selfDelegation sdkmath.Int) stakingtypes.Validator {
// Create a validator
err := suite.deliverMsgCreateValidator(suite.Ctx, addr, suite.NewBondCoin(selfDelegation))
suite.Require().NoError(err)
// New validators are created in an unbonded state. Note if the end blocker is run later this validator could become bonded.
validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, addr)
suite.Require().True(found)
return validator
}
// SlashValidator burns tokens staked in a validator. new_tokens = old_tokens * (1-slashFraction)
func (suite *Suite) SlashValidator(addr sdk.ValAddress, slashFraction sdk.Dec) {
validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, addr)
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 := suite.StakingKeeper.TokensToConsensusPower(suite.Ctx, validator.GetTokens())
suite.StakingKeeper.Slash(suite.Ctx, consAddr, infractionHeight, power, slashFraction)
}
// CreateDelegation delegates tokens to a validator.
func (suite *Suite) CreateDelegation(valAddr sdk.ValAddress, delegator sdk.AccAddress, amount sdkmath.Int) sdk.Dec {
stakingDenom := suite.StakingKeeper.BondDenom(suite.Ctx)
msg := stakingtypes.NewMsgDelegate(
delegator,
valAddr,
sdk.NewCoin(stakingDenom, amount),
)
msgServer := stakingkeeper.NewMsgServerImpl(suite.StakingKeeper)
_, err := msgServer.Delegate(sdk.WrapSDKContext(suite.Ctx), msg)
suite.Require().NoError(err)
del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr)
suite.Require().True(found)
return del.Shares
}
// DelegationSharesEqual checks if a delegation has the specified shares.
// It expects delegations with zero shares to not be stored in state.
func (suite *Suite) DelegationSharesEqual(valAddr sdk.ValAddress, delegator sdk.AccAddress, shares sdk.Dec) bool {
del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr)
if shares.IsZero() {
return suite.Falsef(found, "expected delegator to not be found, got %s shares", del.Shares)
} else {
res := suite.True(found, "expected delegator to be found")
return res && suite.Truef(shares.Equal(del.Shares), "expected %s delegator shares but got %s", shares, del.Shares)
}
}
// DelegationBalanceLessThan checks if a delegation's staked token balance is less the specified amount.
// It treats not found delegations as having zero shares.
func (suite *Suite) DelegationBalanceLessThan(valAddr sdk.ValAddress, delegator sdk.AccAddress, max sdkmath.Int) bool {
shares := sdk.ZeroDec()
del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr)
if found {
shares = del.Shares
}
val, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr)
suite.Require().Truef(found, "expected validator to be found")
tokens := val.TokensFromShares(shares).TruncateInt()
return suite.Truef(tokens.LT(max), "expected delegation balance to be less than %s, got %s", max, tokens)
}
// DelegationBalanceInDeltaBelow checks if a delegation's staked token balance is between `expected` and `expected - delta` inclusive.
// It treats not found delegations as having zero shares.
func (suite *Suite) DelegationBalanceInDeltaBelow(valAddr sdk.ValAddress, delegator sdk.AccAddress, expected, delta sdkmath.Int) bool {
shares := sdk.ZeroDec()
del, found := suite.StakingKeeper.GetDelegation(suite.Ctx, delegator, valAddr)
if found {
shares = del.Shares
}
val, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr)
suite.Require().Truef(found, "expected validator to be found")
tokens := val.TokensFromShares(shares).TruncateInt()
lte := suite.Truef(tokens.LTE(expected), "expected delegation balance to be less than or equal to %s, got %s", expected, tokens)
gte := suite.Truef(tokens.GTE(expected.Sub(delta)), "expected delegation balance to be greater than or equal to %s, got %s", expected.Sub(delta), tokens)
return lte && gte
}
// UnbondingDelegationInDeltaBelow checks if the total balance in an unbonding delegation is between `expected` and `expected - delta` inclusive.
func (suite *Suite) UnbondingDelegationInDeltaBelow(valAddr sdk.ValAddress, delegator sdk.AccAddress, expected, delta sdkmath.Int) bool {
tokens := sdk.ZeroInt()
ubd, found := suite.StakingKeeper.GetUnbondingDelegation(suite.Ctx, delegator, valAddr)
if found {
for _, entry := range ubd.Entries {
tokens = tokens.Add(entry.Balance)
}
}
lte := suite.Truef(tokens.LTE(expected), "expected unbonding delegation balance to be less than or equal to %s, got %s", expected, tokens)
gte := suite.Truef(tokens.GTE(expected.Sub(delta)), "expected unbonding delegation balance to be greater than or equal to %s, got %s", expected.Sub(delta), tokens)
return lte && gte
}
func (suite *Suite) QueryStaking_Delegation(valAddr sdk.ValAddress, delegator sdk.AccAddress) stakingtypes.DelegationResponse {
stakingQuery := stakingkeeper.Querier{Keeper: suite.StakingKeeper}
res, err := stakingQuery.Delegation(
sdk.WrapSDKContext(suite.Ctx),
&stakingtypes.QueryDelegationRequest{
DelegatorAddr: delegator.String(),
ValidatorAddr: valAddr.String(),
},
)
suite.Require().NoError(err)
return *res.DelegationResponse
}
// EventsContains asserts that the expected event is in the provided events
func (suite *Suite) EventsContains(events sdk.Events, expectedEvent sdk.Event) {
foundMatch := false
for _, event := range events {
if event.Type == expectedEvent.Type {
if reflect.DeepEqual(attrsToMap(expectedEvent.Attributes), attrsToMap(event.Attributes)) {
foundMatch = true
}
}
}
suite.True(foundMatch, fmt.Sprintf("event of type %s not found or did not match", expectedEvent.Type))
}
func attrsToMap(attrs []abci.EventAttribute) []sdk.Attribute {
out := []sdk.Attribute{}
for _, attr := range attrs {
out = append(out, sdk.NewAttribute(string(attr.Key), string(attr.Value)))
}
return out
}
// CreateVault adds a new earn vault to the earn keeper parameters
func (suite *Suite) CreateVault(
vaultDenom string,
vaultStrategies earntypes.StrategyTypes,
isPrivateVault bool,
allowedDepositors []sdk.AccAddress,
) {
vault := earntypes.NewAllowedVault(vaultDenom, vaultStrategies, isPrivateVault, allowedDepositors)
allowedVaults := suite.EarnKeeper.GetAllowedVaults(suite.Ctx)
allowedVaults = append(allowedVaults, vault)
params := earntypes.NewParams(allowedVaults)
suite.EarnKeeper.SetParams(
suite.Ctx,
params,
)
}
// SetSavingsSupportedDenoms overwrites the list of supported denoms in the savings module params.
func (suite *Suite) SetSavingsSupportedDenoms(denoms []string) {
sk := suite.App.GetSavingsKeeper()
sk.SetParams(suite.Ctx, savingstypes.NewParams(denoms))
}
// VaultAccountValueEqual asserts that the vault account value matches the provided coin amount.
func (suite *Suite) VaultAccountValueEqual(acc sdk.AccAddress, coin sdk.Coin) {
accVaultBal, err := suite.EarnKeeper.GetVaultAccountValue(suite.Ctx, coin.Denom, acc)
suite.Require().NoError(err)
suite.Require().Truef(
coin.Equal(accVaultBal),
"expected account vault balance to equal %s, but got %s",
coin, accVaultBal,
)
}
// VaultAccountSharesEqual asserts that the vault account shares match the provided values.
func (suite *Suite) VaultAccountSharesEqual(acc sdk.AccAddress, shares earntypes.VaultShares) { // TODO
accVaultShares, found := suite.EarnKeeper.GetVaultAccountShares(suite.Ctx, acc)
if !found {
suite.Empty(shares)
} else {
suite.Equal(shares, accVaultShares)
}
}
func (suite *Suite) QueryEarn_VaultValue(depositor sdk.AccAddress, vaultDenom string) earntypes.DepositResponse {
earnQuery := earnkeeper.NewQueryServerImpl(suite.EarnKeeper)
res, err := earnQuery.Deposits(
sdk.WrapSDKContext(suite.Ctx),
&earntypes.QueryDepositsRequest{
Depositor: depositor.String(),
Denom: vaultDenom,
},
)
suite.Require().NoError(err)
suite.Require().Equalf(1, len(res.Deposits), "while earn supports one vault per denom, deposits response should be length 1")
return res.Deposits[0]
}