Skip to content

Commit

Permalink
feat(x/precisebank): Add remainder amount to genesis (#1911)
Browse files Browse the repository at this point in the history
- Validate total fractional amounts in genesis type
- Validate against fractional balances such that `(sum(balances) + remainder) % conversionFactor == 0`
- Add new utility type `SplitBalance` for splitting up full balances into each
  • Loading branch information
drklee3 authored May 15, 2024
1 parent 94914d4 commit 025b7b2
Show file tree
Hide file tree
Showing 15 changed files with 831 additions and 105 deletions.
4 changes: 3 additions & 1 deletion ci/env/kava-protonet/genesis.json
Original file line number Diff line number Diff line change
Expand Up @@ -3006,7 +3006,9 @@
},
"in_flight_packets": {}
},
"precisebank": {},
"precisebank": {
"remainder": "0"
},
"pricefeed": {
"params": {
"markets": [
Expand Down
1 change: 1 addition & 0 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -6660,6 +6660,7 @@ GenesisState defines the precisebank module's genesis state.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `balances` | [FractionalBalance](#kava.precisebank.v1.FractionalBalance) | repeated | balances is a list of all the balances in the precisebank module. |
| `remainder` | [string](#string) | | remainder is an internal value of how much extra fractional digits are still backed by the reserve, but not assigned to any account. |



Expand Down
8 changes: 8 additions & 0 deletions proto/kava/precisebank/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ message GenesisState {
(gogoproto.castrepeated) = "FractionalBalances",
(gogoproto.nullable) = false
];

// remainder is an internal value of how much extra fractional digits are
// still backed by the reserve, but not assigned to any account.
string remainder = 2 [
(cosmos_proto.scalar) = "cosmos.Int",
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false
];
}

// FractionalBalance defines the fractional portion of an account balance
Expand Down
32 changes: 26 additions & 6 deletions x/precisebank/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package precisebank
import (
"fmt"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/kava-labs/kava/x/precisebank/keeper"
Expand All @@ -14,24 +15,43 @@ func InitGenesis(
ctx sdk.Context,
keeper keeper.Keeper,
ak types.AccountKeeper,
bk types.BankKeeper,
gs *types.GenesisState,
) {
// Ensure the genesis state is valid
if err := gs.Validate(); err != nil {
panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err))
}

// initialize module account
// Initialize module account
if moduleAcc := ak.GetModuleAccount(ctx, types.ModuleName); moduleAcc == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
}

// TODO:
// - Set balances
// - Ensure reserve account exists
// - Ensure reserve balance matches sum of all fractional balances
// Check module balance matches sum of fractional balances + remainder
// This is always a whole integer amount, as previously verified in
// GenesisState.Validate()
totalAmt := gs.TotalAmountWithRemainder()

moduleAddr := ak.GetModuleAddress(types.ModuleName)
moduleBal := bk.GetBalance(ctx, moduleAddr, types.IntegerCoinDenom)
moduleBalExtended := moduleBal.Amount.Mul(types.ConversionFactor())

// Compare balances in full precise extended amounts
if !totalAmt.Equal(moduleBalExtended) {
panic(fmt.Sprintf(
"module account balance does not match sum of fractional balances and remainder, balance is %s but expected %v%s (%v%s)",
moduleBal,
totalAmt, types.ExtendedCoinDenom,
totalAmt.Quo(types.ConversionFactor()), types.IntegerCoinDenom,
))
}

// TODO: After keeper methods are implemented
// - Set account FractionalBalances
}

// ExportGenesis returns a GenesisState for a given context and keeper.
func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState {
return types.NewGenesisState(nil)
return types.NewGenesisState(types.FractionalBalances{}, sdkmath.ZeroInt())
}
156 changes: 134 additions & 22 deletions x/precisebank/genesis_test.go
Original file line number Diff line number Diff line change
@@ -1,73 +1,183 @@
package precisebank_test

import (
"testing"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/kava-labs/kava/x/precisebank"
"github.com/kava-labs/kava/x/precisebank/testutil"
"github.com/kava-labs/kava/x/precisebank/types"
"github.com/stretchr/testify/suite"
)

type KeeperTestSuite struct {
type GenesisTestSuite struct {
testutil.Suite
}

func (suite *KeeperTestSuite) TestInitGenesis() {
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

func (suite *GenesisTestSuite) TestInitGenesis() {
tests := []struct {
name string
setupFn func()
genesisState *types.GenesisState
shouldPanic bool
panicMsg string
}{
{
"default genesisState",
"valid - default genesisState",
func() {},
types.DefaultGenesisState(),
false,
"",
},
{
"empty genesisState",
"valid - empty genesisState",
func() {},
&types.GenesisState{},
false,
"failed to validate precisebank genesis state: nil remainder amount",
},
{
"valid - module balance matches non-zero amount",
func() {
// Set module account balance to expected amount
err := suite.BankKeeper.MintCoins(
suite.Ctx,
types.ModuleName,
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(2))),
)
suite.Require().NoError(err)
},
types.NewGenesisState(
types.FractionalBalances{
types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().SubRaw(1)),
types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().SubRaw(1)),
},
// 2 leftover from 0.999... + 0.999...
sdkmath.NewInt(2),
),
"",
},
{
"TODO: invalid genesisState",
&types.GenesisState{},
false,
// Other GenesisState.Validate() tests are in types/genesis_test.go
"invalid genesisState - GenesisState.Validate() is called",
func() {},
types.NewGenesisState(
types.FractionalBalances{
types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
types.NewFractionalBalance(sdk.AccAddress{1}.String(), sdkmath.NewInt(1)),
},
sdkmath.ZeroInt(),
),
"failed to validate precisebank genesis state: invalid balances: duplicate address kava1qy0xn7za",
},
{
"invalid - module balance insufficient",
func() {},
types.NewGenesisState(
types.FractionalBalances{
types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().SubRaw(1)),
types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().SubRaw(1)),
},
// 2 leftover from 0.999... + 0.999...
sdkmath.NewInt(2),
),
"module account balance does not match sum of fractional balances and remainder, balance is 0ukava but expected 2000000000000akava (2ukava)",
},
{
"invalid - module balance excessive",
func() {
// Set module account balance to greater than expected amount
err := suite.BankKeeper.MintCoins(
suite.Ctx,
types.ModuleName,
sdk.NewCoins(sdk.NewCoin(types.IntegerCoinDenom, sdkmath.NewInt(100))),
)
suite.Require().NoError(err)
},
types.NewGenesisState(
types.FractionalBalances{
types.NewFractionalBalance(sdk.AccAddress{1}.String(), types.ConversionFactor().SubRaw(1)),
types.NewFractionalBalance(sdk.AccAddress{2}.String(), types.ConversionFactor().SubRaw(1)),
},
sdkmath.NewInt(2),
),
"module account balance does not match sum of fractional balances and remainder, balance is 100ukava but expected 2000000000000akava (2ukava)",
},
{
"sets module account",
func() {
// Delete the module account first to ensure it's created here
moduleAcc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
suite.AccountKeeper.RemoveAccount(suite.Ctx, moduleAcc)

// Ensure module account is deleted in state.
// GetModuleAccount() will always return non-nil and does not
// necessarily equate to the account being stored in the account store.
suite.Require().Nil(suite.AccountKeeper.GetAccount(suite.Ctx, moduleAcc.GetAddress()))
},
types.DefaultGenesisState(),
"",
},
}

for _, tc := range tests {
suite.Run(tc.name, func() {
if tc.shouldPanic {
suite.Require().Panics(func() {
precisebank.InitGenesis(suite.Ctx, suite.Keeper, suite.AccountKeeper, tc.genesisState)
}, tc.panicMsg)
suite.SetupTest()
tc.setupFn()

if tc.panicMsg != "" {
suite.Require().PanicsWithValue(
tc.panicMsg,
func() {
precisebank.InitGenesis(
suite.Ctx,
suite.Keeper,
suite.AccountKeeper,
suite.BankKeeper,
tc.genesisState,
)
},
)

return
}

suite.Require().NotPanics(func() {
precisebank.InitGenesis(suite.Ctx, suite.Keeper, suite.AccountKeeper, tc.genesisState)
precisebank.InitGenesis(
suite.Ctx,
suite.Keeper,
suite.AccountKeeper,
suite.BankKeeper,
tc.genesisState,
)
})

// Ensure module account is created
moduleAcc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName)
suite.NotNil(moduleAcc, "module account should be created")
suite.NotNil(moduleAcc)
suite.NotNil(
suite.AccountKeeper.GetAccount(suite.Ctx, moduleAcc.GetAddress()),
"module account should be created & stored in account store",
)

// TODO: Check module state once implemented

// - Verify balances
// - Ensure reserve account exists
// - Ensure reserve balance matches sum of all fractional balances
// Verify balances
// IterateBalances() or something

// Ensure reserve balance matches sum of all fractional balances
// sum up IterateBalances()

// - etc
})
}
}

func (suite *KeeperTestSuite) TestExportGenesis_Valid() {
func (suite *GenesisTestSuite) TestExportGenesis_Valid() {
// ExportGenesis(moduleState) should return a valid genesis state

tests := []struct {
name string
maleate func()
Expand All @@ -79,6 +189,7 @@ func (suite *KeeperTestSuite) TestExportGenesis_Valid() {
suite.Ctx,
suite.Keeper,
suite.AccountKeeper,
suite.BankKeeper,
types.DefaultGenesisState(),
)
},
Expand All @@ -96,7 +207,7 @@ func (suite *KeeperTestSuite) TestExportGenesis_Valid() {
}
}

func (suite *KeeperTestSuite) TestExportImportedState() {
func (suite *GenesisTestSuite) TestExportImportedState() {
// ExportGenesis(InitGenesis(genesisState)) == genesisState

tests := []struct {
Expand All @@ -116,6 +227,7 @@ func (suite *KeeperTestSuite) TestExportImportedState() {
suite.Ctx,
suite.Keeper,
suite.AccountKeeper,
suite.BankKeeper,
tc.initGenesisState,
)
})
Expand Down
2 changes: 1 addition & 1 deletion x/precisebank/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.Ra
// Initialize global index to index in genesis state
cdc.MustUnmarshalJSON(gs, &genState)

InitGenesis(ctx, am.keeper, am.accountKeeper, &genState)
InitGenesis(ctx, am.keeper, am.accountKeeper, am.bankKeeper, &genState)
return []abci.ValidatorUpdate{}
}

Expand Down
Loading

0 comments on commit 025b7b2

Please sign in to comment.