-
Notifications
You must be signed in to change notification settings - Fork 367
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(x/precisebank): Ensure exact reserve balance on integer carry whe…
…n 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.
- Loading branch information
Showing
5 changed files
with
326 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
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) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.