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)
|
||||
- [MsgDeposit](#kava.savings.v1beta1.MsgDeposit)
|
||||
- [MsgDepositResponse](#kava.savings.v1beta1.MsgDepositResponse)
|
||||
- [MsgWithdraw](#kava.savings.v1beta1.MsgWithdraw)
|
||||
- [MsgWithdrawResponse](#kava.savings.v1beta1.MsgWithdrawResponse)
|
||||
|
||||
- [Msg](#kava.savings.v1beta1.Msg)
|
||||
|
||||
@ -5086,6 +5088,7 @@ GenesisState defines the savings module's genesis state.
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `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 enums -->
|
||||
@ -5237,6 +5266,7 @@ Msg defines the savings Msg service.
|
||||
| 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 | |
|
||||
| `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 -->
|
||||
|
||||
|
@ -10,12 +10,11 @@ option go_package = "github.com/kava-labs/kava/x/savings/types";
|
||||
// Msg defines the savings Msg service.
|
||||
service Msg {
|
||||
|
||||
// Deposit defines a method for depositing funds to the savings module account
|
||||
rpc Deposit(MsgDeposit) returns (MsgDepositResponse);
|
||||
|
||||
// Withdraw defines a method for withdrawing funds to the savings module account
|
||||
rpc Withdraw(MsgWithdraw) returns (MsgWithdrawResponse);
|
||||
// Deposit defines a method for depositing funds to the savings module account
|
||||
rpc Deposit(MsgDeposit) returns (MsgDepositResponse);
|
||||
|
||||
// Withdraw defines a method for withdrawing funds to the savings module account
|
||||
rpc Withdraw(MsgWithdraw) returns (MsgWithdrawResponse);
|
||||
}
|
||||
|
||||
// MsgDeposit defines the Msg/Deposit request type.
|
||||
@ -30,10 +29,10 @@ message MsgDepositResponse {}
|
||||
|
||||
// MsgWithdraw defines the Msg/Withdraw request type.
|
||||
message MsgWithdraw {
|
||||
string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
repeated cosmos.base.v1beta1.Coin amount = 2
|
||||
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false];
|
||||
}
|
||||
string depositor = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
|
||||
repeated cosmos.base.v1beta1.Coin amount = 2
|
||||
[(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
// MsgWithdrawResponse defines the Msg/Withdraw response type.
|
||||
message MsgWithdrawResponse {}
|
||||
// MsgWithdrawResponse defines the Msg/Withdraw response type.
|
||||
message MsgWithdrawResponse {}
|
||||
|
@ -42,20 +42,26 @@ func (suite *GenesisTestSuite) TestInitExportGenesis() {
|
||||
[]string{"btc", "ukava", "bnb"},
|
||||
)
|
||||
|
||||
depositAmt := sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e8)))
|
||||
|
||||
deposits := types.Deposits{
|
||||
types.NewDeposit(
|
||||
suite.addrs[0],
|
||||
sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e8))), // 100 ukava
|
||||
depositAmt, // 100 ukava
|
||||
),
|
||||
}
|
||||
|
||||
savingsGenesis := types.NewGenesisState(params, deposits)
|
||||
|
||||
authBuilder := app.NewAuthBankGenesisBuilder().
|
||||
WithSimpleModuleAccount(types.ModuleAccountName, depositAmt)
|
||||
|
||||
cdc := suite.app.AppCodec()
|
||||
suite.NotPanics(
|
||||
func() {
|
||||
suite.app.InitializeFromGenesisStatesWithTime(
|
||||
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/store/prefix"
|
||||
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"
|
||||
"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
|
||||
func (k Keeper) GetDeposit(ctx sdk.Context, depositor sdk.AccAddress) (types.Deposit, bool) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.key), types.DepositsKeyPrefix)
|
||||
|
@ -111,7 +111,9 @@ func (am AppModule) Name() string {
|
||||
}
|
||||
|
||||
// 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
|
||||
func (am AppModule) Route() sdk.Route {
|
||||
|
Loading…
Reference in New Issue
Block a user