mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
Savings module invariants (#1199)
* add deposits to genesis state * import/export genesis with deposits * add helper keeper method + update tests * invariants + tests * register invariants on module * fix genesis test invariant init * clean up invariants test * remove comment from test file * fix invariants test * run 'make proto-all'
This commit is contained in:
parent
003b040458
commit
988836dee0
File diff suppressed because it is too large
Load Diff
@ -375,6 +375,8 @@
|
|||||||
- [kava/savings/v1beta1/tx.proto](#kava/savings/v1beta1/tx.proto)
|
- [kava/savings/v1beta1/tx.proto](#kava/savings/v1beta1/tx.proto)
|
||||||
- [MsgDeposit](#kava.savings.v1beta1.MsgDeposit)
|
- [MsgDeposit](#kava.savings.v1beta1.MsgDeposit)
|
||||||
- [MsgDepositResponse](#kava.savings.v1beta1.MsgDepositResponse)
|
- [MsgDepositResponse](#kava.savings.v1beta1.MsgDepositResponse)
|
||||||
|
- [MsgWithdraw](#kava.savings.v1beta1.MsgWithdraw)
|
||||||
|
- [MsgWithdrawResponse](#kava.savings.v1beta1.MsgWithdrawResponse)
|
||||||
|
|
||||||
- [Msg](#kava.savings.v1beta1.Msg)
|
- [Msg](#kava.savings.v1beta1.Msg)
|
||||||
|
|
||||||
@ -5086,6 +5088,7 @@ GenesisState defines the savings module's genesis state.
|
|||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `params` | [Params](#kava.savings.v1beta1.Params) | | params defines all the parameters of the module. |
|
| `params` | [Params](#kava.savings.v1beta1.Params) | | params defines all the parameters of the module. |
|
||||||
|
| `deposits` | [Deposit](#kava.savings.v1beta1.Deposit) | repeated | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -5222,6 +5225,32 @@ MsgDepositResponse defines the Msg/Deposit response type.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="kava.savings.v1beta1.MsgWithdraw"></a>
|
||||||
|
|
||||||
|
### MsgWithdraw
|
||||||
|
MsgWithdraw defines the Msg/Withdraw request type.
|
||||||
|
|
||||||
|
|
||||||
|
| Field | Type | Label | Description |
|
||||||
|
| ----- | ---- | ----- | ----------- |
|
||||||
|
| `depositor` | [string](#string) | | |
|
||||||
|
| `amount` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<a name="kava.savings.v1beta1.MsgWithdrawResponse"></a>
|
||||||
|
|
||||||
|
### MsgWithdrawResponse
|
||||||
|
MsgWithdrawResponse defines the Msg/Withdraw response type.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- end messages -->
|
<!-- end messages -->
|
||||||
|
|
||||||
<!-- end enums -->
|
<!-- end enums -->
|
||||||
@ -5237,6 +5266,7 @@ Msg defines the savings Msg service.
|
|||||||
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
|
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
|
||||||
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
|
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
|
||||||
| `Deposit` | [MsgDeposit](#kava.savings.v1beta1.MsgDeposit) | [MsgDepositResponse](#kava.savings.v1beta1.MsgDepositResponse) | Deposit defines a method for depositing funds to the savings module account | |
|
| `Deposit` | [MsgDeposit](#kava.savings.v1beta1.MsgDeposit) | [MsgDepositResponse](#kava.savings.v1beta1.MsgDepositResponse) | Deposit defines a method for depositing funds to the savings module account | |
|
||||||
|
| `Withdraw` | [MsgWithdraw](#kava.savings.v1beta1.MsgWithdraw) | [MsgWithdrawResponse](#kava.savings.v1beta1.MsgWithdrawResponse) | Withdraw defines a method for withdrawing funds to the savings module account | |
|
||||||
|
|
||||||
<!-- end services -->
|
<!-- end services -->
|
||||||
|
|
||||||
|
@ -10,12 +10,11 @@ option go_package = "github.com/kava-labs/kava/x/savings/types";
|
|||||||
// Msg defines the savings Msg service.
|
// Msg defines the savings Msg service.
|
||||||
service Msg {
|
service Msg {
|
||||||
|
|
||||||
// Deposit defines a method for depositing funds to the savings module account
|
// Deposit defines a method for depositing funds to the savings module account
|
||||||
rpc Deposit(MsgDeposit) returns (MsgDepositResponse);
|
rpc Deposit(MsgDeposit) returns (MsgDepositResponse);
|
||||||
|
|
||||||
// Withdraw defines a method for withdrawing funds to the savings module account
|
|
||||||
rpc Withdraw(MsgWithdraw) returns (MsgWithdrawResponse);
|
|
||||||
|
|
||||||
|
// Withdraw defines a method for withdrawing funds to the savings module account
|
||||||
|
rpc Withdraw(MsgWithdraw) returns (MsgWithdrawResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgDeposit defines the Msg/Deposit request type.
|
// MsgDeposit defines the Msg/Deposit request type.
|
||||||
@ -30,10 +29,10 @@ message MsgDepositResponse {}
|
|||||||
|
|
||||||
// MsgWithdraw defines the Msg/Withdraw request type.
|
// MsgWithdraw defines the Msg/Withdraw request type.
|
||||||
message MsgWithdraw {
|
message MsgWithdraw {
|
||||||
string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||||
repeated cosmos.base.v1beta1.Coin amount = 2
|
repeated cosmos.base.v1beta1.Coin amount = 2
|
||||||
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false];
|
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false];
|
||||||
}
|
}
|
||||||
|
|
||||||
// MsgWithdrawResponse defines the Msg/Withdraw response type.
|
// MsgWithdrawResponse defines the Msg/Withdraw response type.
|
||||||
message MsgWithdrawResponse {}
|
message MsgWithdrawResponse {}
|
||||||
|
@ -42,20 +42,26 @@ func (suite *GenesisTestSuite) TestInitExportGenesis() {
|
|||||||
[]string{"btc", "ukava", "bnb"},
|
[]string{"btc", "ukava", "bnb"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
depositAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e8)))
|
||||||
|
|
||||||
deposits := types.Deposits{
|
deposits := types.Deposits{
|
||||||
types.NewDeposit(
|
types.NewDeposit(
|
||||||
suite.addrs[0],
|
suite.addrs[0],
|
||||||
sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e8))), // 100 ukava
|
depositAmt, // 100 ukava
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
savingsGenesis := types.NewGenesisState(params, deposits)
|
savingsGenesis := types.NewGenesisState(params, deposits)
|
||||||
|
|
||||||
|
authBuilder := app.NewAuthBankGenesisBuilder().
|
||||||
|
WithSimpleModuleAccount(types.ModuleAccountName, depositAmt)
|
||||||
|
|
||||||
|
cdc := suite.app.AppCodec()
|
||||||
suite.NotPanics(
|
suite.NotPanics(
|
||||||
func() {
|
func() {
|
||||||
suite.app.InitializeFromGenesisStatesWithTime(
|
suite.app.InitializeFromGenesisStatesWithTime(
|
||||||
suite.genTime,
|
suite.genTime,
|
||||||
app.GenesisState{types.ModuleName: suite.app.AppCodec().MustMarshalJSON(&savingsGenesis)},
|
authBuilder.BuildMarshalled(cdc),
|
||||||
|
app.GenesisState{types.ModuleName: cdc.MustMarshalJSON(&savingsGenesis)},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
67
x/savings/keeper/invariants.go
Normal file
67
x/savings/keeper/invariants.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package keeper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kava-labs/kava/x/savings/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterInvariants registers the savings module invariants
|
||||||
|
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
|
||||||
|
ir.RegisterRoute(types.ModuleName, "deposits", DepositsInvariant(k))
|
||||||
|
ir.RegisterRoute(types.ModuleName, "solvency", SolvencyInvariant(k))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllInvariants runs all invariants of the savings module
|
||||||
|
func AllInvariants(k Keeper) sdk.Invariant {
|
||||||
|
return func(ctx sdk.Context) (string, bool) {
|
||||||
|
if res, stop := DepositsInvariant(k)(ctx); stop {
|
||||||
|
return res, stop
|
||||||
|
}
|
||||||
|
|
||||||
|
res, stop := SolvencyInvariant(k)(ctx)
|
||||||
|
return res, stop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepositsInvariant iterates all deposits and asserts that they are valid
|
||||||
|
func DepositsInvariant(k Keeper) sdk.Invariant {
|
||||||
|
broken := false
|
||||||
|
message := sdk.FormatInvariant(types.ModuleName, "validate deposits broken", "deposit invalid")
|
||||||
|
|
||||||
|
return func(ctx sdk.Context) (string, bool) {
|
||||||
|
k.IterateDeposits(ctx, func(deposit types.Deposit) bool {
|
||||||
|
if err := deposit.Validate(); err != nil {
|
||||||
|
broken = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if !deposit.Amount.IsAllPositive() {
|
||||||
|
broken = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
return message, broken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SolvencyInvariant iterates all deposits and ensures the total amount matches the module account coins
|
||||||
|
func SolvencyInvariant(k Keeper) sdk.Invariant {
|
||||||
|
message := sdk.FormatInvariant(types.ModuleName, "module solvency broken", "total deposited amount does not match module account")
|
||||||
|
|
||||||
|
return func(ctx sdk.Context) (string, bool) {
|
||||||
|
balance := k.bankKeeper.GetAllBalances(ctx, k.GetSavingsModuleAccount(ctx).GetAddress())
|
||||||
|
|
||||||
|
deposited := sdk.Coins{}
|
||||||
|
k.IterateDeposits(ctx, func(deposit types.Deposit) bool {
|
||||||
|
for _, coin := range deposit.Amount {
|
||||||
|
deposited = deposited.Add(coin)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
|
||||||
|
broken := !deposited.IsEqual(balance)
|
||||||
|
return message, broken
|
||||||
|
}
|
||||||
|
}
|
149
x/savings/keeper/invariants_test.go
Normal file
149
x/savings/keeper/invariants_test.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package keeper_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||||
|
tmtime "github.com/tendermint/tendermint/types/time"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/simapp"
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/app"
|
||||||
|
"github.com/kava-labs/kava/x/savings/keeper"
|
||||||
|
"github.com/kava-labs/kava/x/savings/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type invariantTestSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
tApp app.TestApp
|
||||||
|
ctx sdk.Context
|
||||||
|
keeper keeper.Keeper
|
||||||
|
bankKeeper bankkeeper.Keeper
|
||||||
|
addrs []sdk.AccAddress
|
||||||
|
invariants map[string]map[string]sdk.Invariant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *invariantTestSuite) SetupTest() {
|
||||||
|
config := sdk.GetConfig()
|
||||||
|
app.SetBech32AddressPrefixes(config)
|
||||||
|
|
||||||
|
tApp := app.NewTestApp()
|
||||||
|
ctx := tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()})
|
||||||
|
|
||||||
|
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||||
|
suite.addrs = addrs
|
||||||
|
|
||||||
|
suite.ctx = ctx
|
||||||
|
suite.keeper = tApp.GetSavingsKeeper()
|
||||||
|
suite.bankKeeper = tApp.GetBankKeeper()
|
||||||
|
|
||||||
|
suite.invariants = make(map[string]map[string]sdk.Invariant)
|
||||||
|
keeper.RegisterInvariants(suite, suite.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *invariantTestSuite) RegisterRoute(moduleName string, route string, invariant sdk.Invariant) {
|
||||||
|
_, exists := suite.invariants[moduleName]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
suite.invariants[moduleName] = make(map[string]sdk.Invariant)
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.invariants[moduleName][route] = invariant
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *invariantTestSuite) SetupValidState() {
|
||||||
|
depositAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(2e8)))
|
||||||
|
|
||||||
|
suite.keeper.SetDeposit(suite.ctx, types.NewDeposit(
|
||||||
|
suite.addrs[0],
|
||||||
|
depositAmt,
|
||||||
|
))
|
||||||
|
|
||||||
|
err := simapp.FundModuleAccount(suite.bankKeeper, suite.ctx, types.ModuleName, depositAmt)
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *invariantTestSuite) runInvariant(route string, invariant func(k keeper.Keeper) sdk.Invariant) (string, bool) {
|
||||||
|
ctx := suite.ctx
|
||||||
|
registeredInvariant := suite.invariants[types.ModuleName][route]
|
||||||
|
suite.Require().NotNil(registeredInvariant)
|
||||||
|
|
||||||
|
// direct call
|
||||||
|
dMessage, dBroken := invariant(suite.keeper)(ctx)
|
||||||
|
// registered call
|
||||||
|
rMessage, rBroken := registeredInvariant(ctx)
|
||||||
|
// all call
|
||||||
|
aMessage, aBroken := keeper.AllInvariants(suite.keeper)(ctx)
|
||||||
|
|
||||||
|
// require matching values for direct call and registered call
|
||||||
|
suite.Require().Equal(dMessage, rMessage, "expected registered invariant message to match")
|
||||||
|
suite.Require().Equal(dBroken, rBroken, "expected registered invariant broken to match")
|
||||||
|
// require matching values for direct call and all invariants call if broken
|
||||||
|
suite.Require().Equal(dBroken, aBroken, "expected all invariant broken to match")
|
||||||
|
if dBroken {
|
||||||
|
suite.Require().Equal(dMessage, aMessage, "expected all invariant message to match")
|
||||||
|
}
|
||||||
|
|
||||||
|
// return message, broken
|
||||||
|
return dMessage, dBroken
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *invariantTestSuite) TestDepositsInvariant() {
|
||||||
|
message, broken := suite.runInvariant("deposits", keeper.DepositsInvariant)
|
||||||
|
suite.Equal("savings: validate deposits broken invariant\ndeposit invalid\n", message)
|
||||||
|
suite.Equal(false, broken)
|
||||||
|
|
||||||
|
suite.SetupValidState()
|
||||||
|
message, broken = suite.runInvariant("deposits", keeper.DepositsInvariant)
|
||||||
|
suite.Equal("savings: validate deposits broken invariant\ndeposit invalid\n", message)
|
||||||
|
suite.Equal(false, broken)
|
||||||
|
|
||||||
|
// broken with invalid deposit
|
||||||
|
suite.keeper.SetDeposit(suite.ctx, types.NewDeposit(
|
||||||
|
suite.addrs[0],
|
||||||
|
sdk.Coins{},
|
||||||
|
))
|
||||||
|
|
||||||
|
message, broken = suite.runInvariant("deposits", keeper.DepositsInvariant)
|
||||||
|
suite.Equal("savings: validate deposits broken invariant\ndeposit invalid\n", message)
|
||||||
|
suite.Equal(true, broken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *invariantTestSuite) TestSolvencyInvariant() {
|
||||||
|
message, broken := suite.runInvariant("solvency", keeper.SolvencyInvariant)
|
||||||
|
suite.Equal("savings: module solvency broken invariant\ntotal deposited amount does not match module account\n", message)
|
||||||
|
suite.Equal(false, broken)
|
||||||
|
|
||||||
|
suite.SetupValidState()
|
||||||
|
message, broken = suite.runInvariant("solvency", keeper.SolvencyInvariant)
|
||||||
|
suite.Equal("savings: module solvency broken invariant\ntotal deposited amount does not match module account\n", message)
|
||||||
|
suite.Equal(false, broken)
|
||||||
|
|
||||||
|
// broken when deposits are greater than module balance
|
||||||
|
suite.keeper.SetDeposit(suite.ctx, types.NewDeposit(
|
||||||
|
suite.addrs[0],
|
||||||
|
sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(3e8))),
|
||||||
|
))
|
||||||
|
|
||||||
|
message, broken = suite.runInvariant("solvency", keeper.SolvencyInvariant)
|
||||||
|
suite.Equal("savings: module solvency broken invariant\ntotal deposited amount does not match module account\n", message)
|
||||||
|
suite.Equal(true, broken)
|
||||||
|
|
||||||
|
// broken when deposits are less than the module balance
|
||||||
|
suite.keeper.SetDeposit(suite.ctx, types.NewDeposit(
|
||||||
|
suite.addrs[0],
|
||||||
|
sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e8))),
|
||||||
|
))
|
||||||
|
|
||||||
|
message, broken = suite.runInvariant("solvency", keeper.SolvencyInvariant)
|
||||||
|
suite.Equal("savings: module solvency broken invariant\ntotal deposited amount does not match module account\n", message)
|
||||||
|
suite.Equal(true, broken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvariantTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(invariantTestSuite))
|
||||||
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
||||||
@ -39,6 +40,11 @@ func NewKeeper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSavingsModuleAccount returns the savings ModuleAccount
|
||||||
|
func (k Keeper) GetSavingsModuleAccount(ctx sdk.Context) authtypes.ModuleAccountI {
|
||||||
|
return k.accountKeeper.GetModuleAccount(ctx, types.ModuleAccountName)
|
||||||
|
}
|
||||||
|
|
||||||
// GetDeposit returns a deposit from the store for a particular depositor address, deposit denom
|
// GetDeposit returns a deposit from the store for a particular depositor address, deposit denom
|
||||||
func (k Keeper) GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (types.Deposit, bool) {
|
func (k Keeper) GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (types.Deposit, bool) {
|
||||||
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositsKeyPrefix)
|
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositsKeyPrefix)
|
||||||
|
@ -111,7 +111,9 @@ func (am AppModule) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RegisterInvariants register module invariants
|
// RegisterInvariants register module invariants
|
||||||
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {}
|
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
|
||||||
|
keeper.RegisterInvariants(ir, am.keeper)
|
||||||
|
}
|
||||||
|
|
||||||
// Route module message route name
|
// Route module message route name
|
||||||
func (am AppModule) Route() sdk.Route {
|
func (am AppModule) Route() sdk.Route {
|
||||||
|
Loading…
Reference in New Issue
Block a user