-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
!feat: Add invartiants #101
Changes from all commits
5246527
7dd5797
9ad8504
fe20826
380b684
6b4b4e6
94b6b95
3f81150
200a1b0
6532533
eb9ba22
e4e9194
45f6ffd
7b13a6c
4191994
0b4798d
2fadd91
dd7c48b
c5dc54b
6642f43
7db6a36
fbf0df5
c42897e
6a23550
7dfaa18
6a8d3cc
e628b52
fd0f2a8
05b1917
107bad4
6bdccfe
6a04ad2
84960d9
feac933
4dc3a19
6fd72d9
de7689b
9d308ae
a9122dc
430b7f5
44c4715
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package keeper | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/realio-tech/multi-staking-module/x/multi-staking/types" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
) | ||
|
||
// RegisterInvariants registers all staking invariants | ||
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { | ||
ir.RegisterRoute(types.ModuleName, "module-accounts", | ||
ModuleAccountInvariants(k)) | ||
ir.RegisterRoute(types.ModuleName, "validator-lock-denom", | ||
ValidatorLockDenomInvariants(k)) | ||
} | ||
|
||
func ModuleAccountInvariants(k Keeper) sdk.Invariant { | ||
return func(ctx sdk.Context) (string, bool) { | ||
totalLockCoinAmount := sdk.NewCoins() | ||
|
||
// calculate lock amount | ||
lockCoinAmount := sdk.NewCoins() | ||
k.MultiStakingLockIterator(ctx, func(stakingLock types.MultiStakingLock) bool { | ||
lockCoinAmount = lockCoinAmount.Add(stakingLock.LockedCoin.ToCoin()) | ||
return false | ||
}) | ||
totalLockCoinAmount = totalLockCoinAmount.Add(lockCoinAmount...) | ||
|
||
// calculate unlocking amount | ||
unlockingCoinAmount := sdk.NewCoins() | ||
k.MultiStakingUnlockIterator(ctx, func(unlock types.MultiStakingUnlock) bool { | ||
for _, entry := range unlock.Entries { | ||
unlockingCoinAmount = unlockingCoinAmount.Add(entry.UnlockingCoin.ToCoin()) | ||
} | ||
return false | ||
}) | ||
totalLockCoinAmount = totalLockCoinAmount.Add(unlockingCoinAmount...) | ||
|
||
moduleAccount := authtypes.NewModuleAddress(types.ModuleName) | ||
escrowBalances := k.bankKeeper.GetAllBalances(ctx, moduleAccount) | ||
|
||
broken := !escrowBalances.IsAllGTE(totalLockCoinAmount) | ||
|
||
return sdk.FormatInvariant( | ||
types.ModuleName, | ||
"ModuleAccountInvariants", | ||
fmt.Sprintf( | ||
"\tescrow coins balances: %v\n"+ | ||
"\ttotal lock coin amount: %v\n", | ||
escrowBalances, totalLockCoinAmount), | ||
), broken | ||
} | ||
} | ||
|
||
func ValidatorLockDenomInvariants(k Keeper) sdk.Invariant { | ||
return func(ctx sdk.Context) (string, bool) { | ||
var ( | ||
msg string | ||
broken bool | ||
) | ||
|
||
var multiStakingLocks []types.MultiStakingLock | ||
k.MultiStakingLockIterator(ctx, func(stakingLock types.MultiStakingLock) bool { | ||
multiStakingLocks = append(multiStakingLocks, stakingLock) | ||
return false | ||
}) | ||
|
||
for _, lock := range multiStakingLocks { | ||
valBench32Addr := lock.LockID.ValAddr | ||
valAddr, _ := sdk.ValAddressFromBech32(valBench32Addr) | ||
if valMsDenom := k.GetValidatorMultiStakingCoin(ctx, valAddr); valMsDenom != lock.LockedCoin.Denom { | ||
broken = true | ||
msg += fmt.Sprintf("validator lock denom invariants:\n\t"+ | ||
"\tlock denom: %v allow denom: %v\n"+ | ||
"\tlock: %v\n", | ||
lock.LockedCoin.Denom, valMsDenom, lock) | ||
} | ||
} | ||
|
||
var multiStakingUnlocks []types.MultiStakingUnlock | ||
k.MultiStakingUnlockIterator(ctx, func(stakingUnlock types.MultiStakingUnlock) bool { | ||
multiStakingUnlocks = append(multiStakingUnlocks, stakingUnlock) | ||
return false | ||
}) | ||
|
||
for _, unlock := range multiStakingUnlocks { | ||
valBench32Addr := unlock.UnlockID.ValAddr | ||
valAddr, _ := sdk.ValAddressFromBech32(valBench32Addr) | ||
valMsDenom := k.GetValidatorMultiStakingCoin(ctx, valAddr) | ||
|
||
for _, entry := range unlock.Entries { | ||
if entry.UnlockingCoin.Denom != valMsDenom { | ||
broken = true | ||
msg += fmt.Sprintf("validator unlock denom invariants:\n\t"+ | ||
"\n\tunlock denom: %v allow denom: %v\n"+ | ||
"\n\t entry height %v"+ | ||
"\n\t validator address %s deladdress %s", | ||
entry.UnlockingCoin.Denom, valMsDenom, entry.CreationHeight, unlock.UnlockID.ValAddr, unlock.UnlockID.MultiStakerAddr) | ||
} | ||
} | ||
} | ||
|
||
return sdk.FormatInvariant(types.ModuleName, "validator lock denom", fmt.Sprintf("found invalid validator lock denom\n%s", msg)), broken | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
package keeper_test | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/realio-tech/multi-staking-module/testutil" | ||
"github.com/realio-tech/multi-staking-module/x/multi-staking/keeper" | ||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types" | ||
|
||
codectypes "github.com/cosmos/cosmos-sdk/codec/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
) | ||
|
||
func (suite *KeeperTestSuite) TestModuleAccountInvariants() { | ||
delAddr := testutil.GenAddress() | ||
priv, valAddr := testutil.GenValAddressWithPrivKey() | ||
valPubKey := priv.PubKey() | ||
|
||
testCases := []struct { | ||
name string | ||
malleate func() | ||
expPass bool | ||
}{ | ||
{ | ||
name: "Success", | ||
malleate: func() {}, | ||
expPass: true, | ||
}, | ||
{ | ||
name: "Success Edit Validator", | ||
malleate: func() { | ||
suite.ctx = suite.ctx.WithBlockHeader(tmproto.Header{Time: time.Now()}) | ||
newRate := sdk.MustNewDecFromStr("0.03") | ||
newMinSelfDelegation := sdk.NewInt(300) | ||
editMsg := stakingtypes.NewMsgEditValidator(valAddr, stakingtypes.Description{ | ||
Moniker: "test 1", | ||
Identity: "test 1", | ||
Website: "test 1", | ||
SecurityContact: "test 1", | ||
Details: "test 1", | ||
}, | ||
&newRate, | ||
&newMinSelfDelegation, | ||
) | ||
_, err := suite.msgServer.EditValidator(suite.ctx, editMsg) | ||
suite.Require().NoError(err) | ||
}, | ||
expPass: true, | ||
}, | ||
{ | ||
name: "Success Delegate", | ||
malleate: func() { | ||
bondAmount := sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(1000)) | ||
delMsg := stakingtypes.NewMsgDelegate(delAddr, valAddr, bondAmount) | ||
_, err := suite.msgServer.Delegate(suite.ctx, delMsg) | ||
suite.Require().NoError(err) | ||
}, | ||
expPass: true, | ||
}, | ||
{ | ||
name: "Success Delegate", | ||
malleate: func() { | ||
bondAmount := sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(1000)) | ||
delMsg := stakingtypes.NewMsgDelegate(delAddr, valAddr, bondAmount) | ||
_, err := suite.msgServer.Delegate(suite.ctx, delMsg) | ||
suite.Require().NoError(err) | ||
}, | ||
expPass: true, | ||
}, | ||
{ | ||
name: "Success BeginRedelegate", | ||
malleate: func() { | ||
priv, valAddr2 := testutil.GenValAddressWithPrivKey() | ||
valPubKey2 := priv.PubKey() | ||
bondAmount := sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(500)) | ||
createMsg2 := stakingtypes.MsgCreateValidator{ | ||
Description: stakingtypes.Description{ | ||
Moniker: "test", | ||
Identity: "test", | ||
Website: "test", | ||
SecurityContact: "test", | ||
Details: "test", | ||
}, | ||
Commission: stakingtypes.CommissionRates{ | ||
Rate: sdk.MustNewDecFromStr("0.05"), | ||
MaxRate: sdk.MustNewDecFromStr("0.1"), | ||
MaxChangeRate: sdk.MustNewDecFromStr("0.1"), | ||
}, | ||
MinSelfDelegation: sdk.NewInt(200), | ||
DelegatorAddress: delAddr.String(), | ||
ValidatorAddress: valAddr2.String(), | ||
Pubkey: codectypes.UnsafePackAny(valPubKey2), | ||
Value: bondAmount, | ||
} | ||
|
||
_, err := suite.msgServer.CreateValidator(suite.ctx, &createMsg2) | ||
suite.Require().NoError(err) | ||
|
||
multiStakingMsg := stakingtypes.NewMsgBeginRedelegate(delAddr, valAddr, valAddr2, bondAmount) | ||
_, err = suite.msgServer.BeginRedelegate(suite.ctx, multiStakingMsg) | ||
suite.Require().NoError(err) | ||
}, | ||
expPass: true, | ||
}, | ||
{ | ||
name: "Success Undelegate", | ||
malleate: func() { | ||
bondAmount := sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(250)) | ||
multiStakingMsg := stakingtypes.NewMsgUndelegate(delAddr, valAddr, bondAmount) | ||
_, err := suite.msgServer.Undelegate(suite.ctx, multiStakingMsg) | ||
suite.Require().NoError(err) | ||
|
||
bondAmount1 := sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(500)) | ||
multiStakingMsg1 := stakingtypes.NewMsgUndelegate(delAddr, valAddr, bondAmount1) | ||
_, err = suite.msgServer.Undelegate(suite.ctx, multiStakingMsg1) | ||
suite.Require().NoError(err) | ||
}, | ||
expPass: true, | ||
}, | ||
} | ||
for _, tc := range testCases { | ||
suite.SetupTest() // reset | ||
|
||
valCoins := sdk.NewCoins(sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(10000)), sdk.NewCoin(MultiStakingDenomB, sdk.NewInt(10000))) | ||
err := suite.FundAccount(delAddr, valCoins) | ||
suite.Require().NoError(err) | ||
|
||
suite.msKeeper.SetBondWeight(suite.ctx, MultiStakingDenomA, sdk.MustNewDecFromStr("0.3")) | ||
bondAmount := sdk.NewCoin(MultiStakingDenomA, sdk.NewInt(3001)) | ||
msg := stakingtypes.MsgCreateValidator{ | ||
Description: stakingtypes.Description{ | ||
Moniker: "test", | ||
Identity: "test", | ||
Website: "test", | ||
SecurityContact: "test", | ||
Details: "test", | ||
}, | ||
Commission: stakingtypes.CommissionRates{ | ||
Rate: sdk.MustNewDecFromStr("0.05"), | ||
MaxRate: sdk.MustNewDecFromStr("0.1"), | ||
MaxChangeRate: sdk.MustNewDecFromStr("0.05"), | ||
}, | ||
MinSelfDelegation: sdk.NewInt(1), | ||
DelegatorAddress: delAddr.String(), | ||
ValidatorAddress: valAddr.String(), | ||
Pubkey: codectypes.UnsafePackAny(valPubKey), | ||
Value: bondAmount, | ||
} | ||
|
||
_, err = suite.msgServer.CreateValidator(suite.ctx, &msg) | ||
suite.Require().NoError(err) | ||
|
||
tc.malleate() | ||
_, broken := keeper.ModuleAccountInvariants(*suite.msKeeper)(suite.ctx) | ||
|
||
if tc.expPass { | ||
suite.Require().False(broken) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,15 +3,19 @@ package keeper_test | |
import ( | ||
"github.com/realio-tech/multi-staking-module/testutil" | ||
multistakingkeeper "github.com/realio-tech/multi-staking-module/x/multi-staking/keeper" | ||
"github.com/realio-tech/multi-staking-module/x/multi-staking/types" | ||
|
||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
var ( | ||
gasDenom = "ario" | ||
govDenom = "arst" | ||
) | ||
|
||
func (suite *KeeperTestSuite) TestSetBondWeight() { | ||
suite.SetupTest() | ||
|
||
gasDenom := "ario" | ||
govDenom := "arst" | ||
gasWeight := sdk.OneDec() | ||
govWeight := sdk.NewDecWithPrec(2, 4) | ||
|
||
|
@@ -28,8 +32,7 @@ func (suite *KeeperTestSuite) TestSetBondWeight() { | |
func (suite *KeeperTestSuite) TestSetValidatorMultiStakingCoin() { | ||
valA := testutil.GenValAddress() | ||
valB := testutil.GenValAddress() | ||
gasDenom := "ario" | ||
govDenom := "arst" | ||
|
||
testCases := []struct { | ||
name string | ||
malleate func(ctx sdk.Context, msKeeper *multistakingkeeper.Keeper) []string | ||
|
@@ -86,3 +89,43 @@ func (suite *KeeperTestSuite) TestSetValidatorMultiStakingCoin() { | |
}) | ||
} | ||
} | ||
|
||
func (suite *KeeperTestSuite) TestSetMultiStakingLock() { | ||
suite.SetupTest() | ||
delAddr := testutil.GenAddress() | ||
valAddr := testutil.GenValAddress() | ||
|
||
lock := types.MultiStakingLock{ | ||
LockID: types.LockID{ | ||
MultiStakerAddr: delAddr.String(), | ||
ValAddr: valAddr.String(), | ||
}, | ||
LockedCoin: types.MultiStakingCoin{ | ||
Denom: gasDenom, | ||
Amount: sdk.NewIntFromUint64(1000000), | ||
BondWeight: sdk.NewDec(1), | ||
}, | ||
} | ||
|
||
testCases := []struct { | ||
name string | ||
malleate func() | ||
expError bool | ||
}{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's no fail case for this test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is different test for check the store, not related in invariants. We should continue this test in different PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there should be fail case anyway because there is tc.expError. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If so I think we should move it to a separate PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it's okay to put the test in this pr |
||
{ | ||
"Success", | ||
func() { | ||
suite.msKeeper.SetMultiStakingLock(suite.ctx, lock) | ||
}, | ||
false, | ||
}, | ||
} | ||
for _, tc := range testCases { | ||
if !tc.expError { | ||
tc.malleate() | ||
msLock, found := suite.msKeeper.GetMultiStakingLock(suite.ctx, lock.LockID) | ||
suite.Require().True(found) | ||
suite.Require().Equal(lock, msLock) | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why there's no fail case in this test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
invariants should be success in any case
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be fail case in test, just set data in the store that causes it to fail