0g-chain/x/precisebank/keeper/invariants_integration_test.go
drklee3 1743cf5275
fix(x/precisebank): Ensure exact reserve balance on integer carry when minting (#1932)
Fix reserve minting an extra coin when the recipient module both carries fractional over to integer balance AND remainder is insufficient. Adjusts fractional carry to simply send from reserve, instead of doing an additional mint. Add invariant to ensure reserve matches exactly with fractional balances + remainder, failing on both insufficient and excess funds.
2024-06-20 15:20:13 -07:00

130 lines
4.0 KiB
Go

package keeper_test
import (
"testing"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/precisebank/keeper"
"github.com/kava-labs/kava/x/precisebank/testutil"
"github.com/kava-labs/kava/x/precisebank/types"
"github.com/stretchr/testify/suite"
)
type invariantsIntegrationTestSuite struct {
testutil.Suite
}
func (suite *invariantsIntegrationTestSuite) SetupTest() {
suite.Suite.SetupTest()
}
func TestInvariantsIntegrationTest(t *testing.T) {
suite.Run(t, new(invariantsIntegrationTestSuite))
}
func (suite *invariantsIntegrationTestSuite) FundReserve(amt sdkmath.Int) {
coins := sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, amt))
err := suite.BankKeeper.MintCoins(suite.Ctx, types.ModuleName, coins)
suite.Require().NoError(err)
}
func (suite *invariantsIntegrationTestSuite) TestReserveBackingFractionalInvariant() {
tests := []struct {
name string
setupFn func(ctx sdk.Context, k keeper.Keeper)
wantBroken bool
wantMsg string
}{
{
"valid - empty state",
func(_ sdk.Context, _ keeper.Keeper) {},
false,
"",
},
{
"valid - fractional balances, no remainder",
func(ctx sdk.Context, k keeper.Keeper) {
k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
k.SetFractionalBalance(ctx, sdk.AccAddress{2}, types.ConversionFactor().QuoRaw(2))
// 1 integer backs same amount fractional
suite.FundReserve(sdk.NewInt(1))
},
false,
"",
},
{
"valid - fractional balances, with remainder",
func(ctx sdk.Context, k keeper.Keeper) {
k.SetFractionalBalance(ctx, sdk.AccAddress{1}, types.ConversionFactor().QuoRaw(2))
k.SetRemainderAmount(ctx, types.ConversionFactor().QuoRaw(2))
// 1 integer backs same amount fractional including remainder
suite.FundReserve(sdk.NewInt(1))
},
false,
"",
},
{
"invalid - no fractional balances, non-zero remainder",
func(ctx sdk.Context, k keeper.Keeper) {
k.SetRemainderAmount(ctx, types.ConversionFactor().QuoRaw(2))
},
true,
"precisebank: module reserve backing total fractional balances invariant\nakava reserve balance 0 mismatches 500000000000 (fractional balances 0 + remainder 500000000000)\n\n",
},
{
"invalid - insufficient reserve backing",
func(ctx sdk.Context, k keeper.Keeper) {
amt := types.ConversionFactor().QuoRaw(2)
// 0.5 int coins x 4
k.SetFractionalBalance(ctx, sdk.AccAddress{1}, amt)
k.SetFractionalBalance(ctx, sdk.AccAddress{2}, amt)
k.SetFractionalBalance(ctx, sdk.AccAddress{3}, amt)
k.SetRemainderAmount(ctx, amt)
// Needs 2 to back 0.5 x 4
suite.FundReserve(sdk.NewInt(1))
},
true,
"precisebank: module reserve backing total fractional balances invariant\nakava reserve balance 1000000000000 mismatches 2000000000000 (fractional balances 1500000000000 + remainder 500000000000)\n\n",
},
{
"invalid - excess reserve backing",
func(ctx sdk.Context, k keeper.Keeper) {
amt := types.ConversionFactor().QuoRaw(2)
// 0.5 int coins x 4
k.SetFractionalBalance(ctx, sdk.AccAddress{1}, amt)
k.SetFractionalBalance(ctx, sdk.AccAddress{2}, amt)
k.SetFractionalBalance(ctx, sdk.AccAddress{3}, amt)
k.SetRemainderAmount(ctx, amt)
// Needs 2 to back 0.5 x 4
suite.FundReserve(sdk.NewInt(3))
},
true,
"precisebank: module reserve backing total fractional balances invariant\nakava reserve balance 3000000000000 mismatches 2000000000000 (fractional balances 1500000000000 + remainder 500000000000)\n\n",
},
}
for _, tt := range tests {
suite.Run(tt.name, func() {
// Reset each time
suite.SetupTest()
tt.setupFn(suite.Ctx, suite.Keeper)
invariantFn := keeper.ReserveBacksFractionsInvariant(suite.Keeper)
msg, broken := invariantFn(suite.Ctx)
if tt.wantBroken {
suite.Require().True(broken, "invariant should be broken but is not")
suite.Require().Equal(tt.wantMsg, msg)
} else {
suite.Require().Falsef(broken, "invariant should not be broken but is: %s", msg)
}
})
}
}