diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b2c2f46..1ced6308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +### Features +- (precisebank) [#1906] Add new `x/precisebank` module with bank decimal extension for EVM usage. + ### Improvements - (rocksdb) [#1903] Bump cometbft-db dependency for use with rocksdb v8.10.0 @@ -333,6 +336,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md). - [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run large-scale simulations remotely using aws-batch +[#1906]: https://github.com/Kava-Labs/kava/pull/1906 [#1903]: https://github.com/Kava-Labs/kava/pull/1903 [#1846]: https://github.com/Kava-Labs/kava/pull/1846 [#1848]: https://github.com/Kava-Labs/kava/pull/1848 diff --git a/app/app.go b/app/app.go index 563ac76b..29fb6ea8 100644 --- a/app/app.go +++ b/app/app.go @@ -149,6 +149,9 @@ import ( liquidtypes "github.com/kava-labs/kava/x/liquid/types" metrics "github.com/kava-labs/kava/x/metrics" metricstypes "github.com/kava-labs/kava/x/metrics/types" + "github.com/kava-labs/kava/x/precisebank" + precisebankkeeper "github.com/kava-labs/kava/x/precisebank/keeper" + precisebanktypes "github.com/kava-labs/kava/x/precisebank/types" pricefeed "github.com/kava-labs/kava/x/pricefeed" pricefeedkeeper "github.com/kava-labs/kava/x/pricefeed/keeper" pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" @@ -230,6 +233,7 @@ var ( community.AppModuleBasic{}, metrics.AppModuleBasic{}, consensus.AppModuleBasic{}, + precisebank.AppModuleBasic{}, ) // module account permissions @@ -258,6 +262,7 @@ var ( kavadisttypes.FundModuleAccount: nil, minttypes.ModuleName: {authtypes.Minter}, communitytypes.ModuleName: nil, + precisebanktypes.ModuleName: {authtypes.Minter, authtypes.Burner}, // used for reserve account to back fractional amounts } ) @@ -335,6 +340,7 @@ type App struct { mintKeeper mintkeeper.Keeper communityKeeper communitykeeper.Keeper consensusParamsKeeper consensusparamkeeper.Keeper + precisebankKeeper precisebankkeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -389,7 +395,7 @@ func NewApp( swaptypes.StoreKey, cdptypes.StoreKey, hardtypes.StoreKey, communitytypes.StoreKey, committeetypes.StoreKey, incentivetypes.StoreKey, evmutiltypes.StoreKey, savingstypes.StoreKey, earntypes.StoreKey, minttypes.StoreKey, - consensusparamtypes.StoreKey, crisistypes.StoreKey, + consensusparamtypes.StoreKey, crisistypes.StoreKey, precisebanktypes.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(paramstypes.TStoreKey, evmtypes.TransientKey, feemarkettypes.TransientKey) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) @@ -544,6 +550,12 @@ func NewApp( app.accountKeeper, ) + // TODO: Pass this to evmkeeper.NewKeeper() instead of evmutilKeeper + app.precisebankKeeper = precisebankkeeper.NewKeeper( + app.appCodec, + keys[precisebanktypes.StoreKey], + ) + evmBankKeeper := evmutilkeeper.NewEvmBankKeeper(app.evmutilKeeper, app.bankKeeper, app.accountKeeper) app.evmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], @@ -849,6 +861,7 @@ func NewApp( mint.NewAppModule(appCodec, app.mintKeeper, app.accountKeeper, nil, mintSubspace), community.NewAppModule(app.communityKeeper, app.accountKeeper), metrics.NewAppModule(options.TelemetryOptions), + precisebank.NewAppModule(app.precisebankKeeper, app.bankKeeper, app.accountKeeper), ) // Warning: Some begin blockers must run before others. Ensure the dependencies are understood before modifying this list. @@ -904,6 +917,7 @@ func NewApp( routertypes.ModuleName, consensusparamtypes.ModuleName, packetforwardtypes.ModuleName, + precisebanktypes.ModuleName, ) // Warning: Some end blockers must run before others. Ensure the dependencies are understood before modifying this list. @@ -949,6 +963,7 @@ func NewApp( metricstypes.ModuleName, consensusparamtypes.ModuleName, packetforwardtypes.ModuleName, + precisebanktypes.ModuleName, ) // Warning: Some init genesis methods must run before others. Ensure the dependencies are understood before modifying this list @@ -992,7 +1007,8 @@ func NewApp( metricstypes.ModuleName, consensusparamtypes.ModuleName, packetforwardtypes.ModuleName, - crisistypes.ModuleName, // runs the invariants at genesis, should run after other modules + precisebanktypes.ModuleName, // Must be run after x/bank to verify reserve balance + crisistypes.ModuleName, // runs the invariants at genesis, should run after other modules ) app.mm.RegisterInvariants(&app.crisisKeeper) diff --git a/app/test_common.go b/app/test_common.go index ca8d7d38..a069e3c8 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -53,6 +53,7 @@ import ( issuancekeeper "github.com/kava-labs/kava/x/issuance/keeper" kavadistkeeper "github.com/kava-labs/kava/x/kavadist/keeper" liquidkeeper "github.com/kava-labs/kava/x/liquid/keeper" + precisebankkeeper "github.com/kava-labs/kava/x/precisebank/keeper" pricefeedkeeper "github.com/kava-labs/kava/x/pricefeed/keeper" routerkeeper "github.com/kava-labs/kava/x/router/keeper" savingskeeper "github.com/kava-labs/kava/x/savings/keeper" @@ -108,33 +109,34 @@ func NewTestAppFromSealed() TestApp { } // nolint -func (tApp TestApp) GetAccountKeeper() authkeeper.AccountKeeper { return tApp.accountKeeper } -func (tApp TestApp) GetBankKeeper() bankkeeper.Keeper { return tApp.bankKeeper } -func (tApp TestApp) GetMintKeeper() mintkeeper.Keeper { return tApp.mintKeeper } -func (tApp TestApp) GetStakingKeeper() *stakingkeeper.Keeper { return tApp.stakingKeeper } -func (tApp TestApp) GetSlashingKeeper() slashingkeeper.Keeper { return tApp.slashingKeeper } -func (tApp TestApp) GetDistrKeeper() distkeeper.Keeper { return tApp.distrKeeper } -func (tApp TestApp) GetGovKeeper() govkeeper.Keeper { return tApp.govKeeper } -func (tApp TestApp) GetCrisisKeeper() crisiskeeper.Keeper { return tApp.crisisKeeper } -func (tApp TestApp) GetParamsKeeper() paramskeeper.Keeper { return tApp.paramsKeeper } -func (tApp TestApp) GetKavadistKeeper() kavadistkeeper.Keeper { return tApp.kavadistKeeper } -func (tApp TestApp) GetAuctionKeeper() auctionkeeper.Keeper { return tApp.auctionKeeper } -func (tApp TestApp) GetIssuanceKeeper() issuancekeeper.Keeper { return tApp.issuanceKeeper } -func (tApp TestApp) GetBep3Keeper() bep3keeper.Keeper { return tApp.bep3Keeper } -func (tApp TestApp) GetPriceFeedKeeper() pricefeedkeeper.Keeper { return tApp.pricefeedKeeper } -func (tApp TestApp) GetSwapKeeper() swapkeeper.Keeper { return tApp.swapKeeper } -func (tApp TestApp) GetCDPKeeper() cdpkeeper.Keeper { return tApp.cdpKeeper } -func (tApp TestApp) GetHardKeeper() hardkeeper.Keeper { return tApp.hardKeeper } -func (tApp TestApp) GetCommitteeKeeper() committeekeeper.Keeper { return tApp.committeeKeeper } -func (tApp TestApp) GetIncentiveKeeper() incentivekeeper.Keeper { return tApp.incentiveKeeper } -func (tApp TestApp) GetEvmutilKeeper() evmutilkeeper.Keeper { return tApp.evmutilKeeper } -func (tApp TestApp) GetEvmKeeper() *evmkeeper.Keeper { return tApp.evmKeeper } -func (tApp TestApp) GetSavingsKeeper() savingskeeper.Keeper { return tApp.savingsKeeper } -func (tApp TestApp) GetFeeMarketKeeper() feemarketkeeper.Keeper { return tApp.feeMarketKeeper } -func (tApp TestApp) GetLiquidKeeper() liquidkeeper.Keeper { return tApp.liquidKeeper } -func (tApp TestApp) GetEarnKeeper() earnkeeper.Keeper { return tApp.earnKeeper } -func (tApp TestApp) GetRouterKeeper() routerkeeper.Keeper { return tApp.routerKeeper } -func (tApp TestApp) GetCommunityKeeper() communitykeeper.Keeper { return tApp.communityKeeper } +func (tApp TestApp) GetAccountKeeper() authkeeper.AccountKeeper { return tApp.accountKeeper } +func (tApp TestApp) GetBankKeeper() bankkeeper.Keeper { return tApp.bankKeeper } +func (tApp TestApp) GetMintKeeper() mintkeeper.Keeper { return tApp.mintKeeper } +func (tApp TestApp) GetStakingKeeper() *stakingkeeper.Keeper { return tApp.stakingKeeper } +func (tApp TestApp) GetSlashingKeeper() slashingkeeper.Keeper { return tApp.slashingKeeper } +func (tApp TestApp) GetDistrKeeper() distkeeper.Keeper { return tApp.distrKeeper } +func (tApp TestApp) GetGovKeeper() govkeeper.Keeper { return tApp.govKeeper } +func (tApp TestApp) GetCrisisKeeper() crisiskeeper.Keeper { return tApp.crisisKeeper } +func (tApp TestApp) GetParamsKeeper() paramskeeper.Keeper { return tApp.paramsKeeper } +func (tApp TestApp) GetKavadistKeeper() kavadistkeeper.Keeper { return tApp.kavadistKeeper } +func (tApp TestApp) GetAuctionKeeper() auctionkeeper.Keeper { return tApp.auctionKeeper } +func (tApp TestApp) GetIssuanceKeeper() issuancekeeper.Keeper { return tApp.issuanceKeeper } +func (tApp TestApp) GetBep3Keeper() bep3keeper.Keeper { return tApp.bep3Keeper } +func (tApp TestApp) GetPriceFeedKeeper() pricefeedkeeper.Keeper { return tApp.pricefeedKeeper } +func (tApp TestApp) GetSwapKeeper() swapkeeper.Keeper { return tApp.swapKeeper } +func (tApp TestApp) GetCDPKeeper() cdpkeeper.Keeper { return tApp.cdpKeeper } +func (tApp TestApp) GetHardKeeper() hardkeeper.Keeper { return tApp.hardKeeper } +func (tApp TestApp) GetCommitteeKeeper() committeekeeper.Keeper { return tApp.committeeKeeper } +func (tApp TestApp) GetIncentiveKeeper() incentivekeeper.Keeper { return tApp.incentiveKeeper } +func (tApp TestApp) GetEvmutilKeeper() evmutilkeeper.Keeper { return tApp.evmutilKeeper } +func (tApp TestApp) GetEvmKeeper() *evmkeeper.Keeper { return tApp.evmKeeper } +func (tApp TestApp) GetSavingsKeeper() savingskeeper.Keeper { return tApp.savingsKeeper } +func (tApp TestApp) GetFeeMarketKeeper() feemarketkeeper.Keeper { return tApp.feeMarketKeeper } +func (tApp TestApp) GetLiquidKeeper() liquidkeeper.Keeper { return tApp.liquidKeeper } +func (tApp TestApp) GetEarnKeeper() earnkeeper.Keeper { return tApp.earnKeeper } +func (tApp TestApp) GetRouterKeeper() routerkeeper.Keeper { return tApp.routerKeeper } +func (tApp TestApp) GetCommunityKeeper() communitykeeper.Keeper { return tApp.communityKeeper } +func (tApp TestApp) GetPrecisebankKeeper() precisebankkeeper.Keeper { return tApp.precisebankKeeper } func (tApp TestApp) GetKVStoreKey(key string) *storetypes.KVStoreKey { return tApp.keys[key] diff --git a/ci/env/kava-protonet/genesis.json b/ci/env/kava-protonet/genesis.json index f252771c..2dc1e288 100644 --- a/ci/env/kava-protonet/genesis.json +++ b/ci/env/kava-protonet/genesis.json @@ -3006,6 +3006,7 @@ }, "in_flight_packets": {} }, + "precisebank": {}, "pricefeed": { "params": { "markets": [ diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 80adc6c3..0aee98cc 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -476,6 +476,9 @@ - [Msg](#kava.liquid.v1beta1.Msg) +- [kava/precisebank/v1/genesis.proto](#kava/precisebank/v1/genesis.proto) + - [GenesisState](#kava.precisebank.v1.GenesisState) + - [kava/pricefeed/v1beta1/store.proto](#kava/pricefeed/v1beta1/store.proto) - [CurrentPrice](#kava.pricefeed.v1beta1.CurrentPrice) - [Market](#kava.pricefeed.v1beta1.Market) @@ -6624,6 +6627,32 @@ Msg defines the liquid Msg service. + +
+ +## kava/precisebank/v1/genesis.proto + + + + + +### GenesisState +GenesisState defines the precisebank module's genesis state. + + + + + + + + + + + + + + + diff --git a/proto/kava/precisebank/v1/genesis.proto b/proto/kava/precisebank/v1/genesis.proto new file mode 100644 index 00000000..5b80179b --- /dev/null +++ b/proto/kava/precisebank/v1/genesis.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; +package kava.precisebank.v1; + +option go_package = "github.com/kava-labs/kava/x/precisebank/types"; + +// GenesisState defines the precisebank module's genesis state. +message GenesisState {} diff --git a/x/precisebank/README.md b/x/precisebank/README.md new file mode 100644 index 00000000..d33fb574 --- /dev/null +++ b/x/precisebank/README.md @@ -0,0 +1,12 @@ +# `x/precisebank` + +## Abstract + +This document specifies the precisebank module of Kava. + +The precisebank module is responsible for extending the precision of `x/bank`, +intended to be used for the `x/evm`. It serves as a wrapper of `x/bank` to +increase the precision of KAVA from 6 to 18 decimals, while preserving the +behavior of existing `x/bank` balances. + +This module is used only by `x/evm` where 18 decimal points are expected. diff --git a/x/precisebank/genesis.go b/x/precisebank/genesis.go new file mode 100644 index 00000000..4bcf0498 --- /dev/null +++ b/x/precisebank/genesis.go @@ -0,0 +1,37 @@ +package precisebank + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/precisebank/keeper" + "github.com/kava-labs/kava/x/precisebank/types" +) + +// InitGenesis initializes the store state from a genesis state. +func InitGenesis( + ctx sdk.Context, + keeper keeper.Keeper, + ak types.AccountKeeper, + gs *types.GenesisState, +) { + if err := gs.Validate(); err != nil { + panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err)) + } + + // 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 +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper keeper.Keeper) *types.GenesisState { + return types.NewGenesisState() +} diff --git a/x/precisebank/genesis_test.go b/x/precisebank/genesis_test.go new file mode 100644 index 00000000..7d052510 --- /dev/null +++ b/x/precisebank/genesis_test.go @@ -0,0 +1,133 @@ +package precisebank_test + +import ( + "github.com/kava-labs/kava/x/precisebank" + "github.com/kava-labs/kava/x/precisebank/testutil" + "github.com/kava-labs/kava/x/precisebank/types" +) + +type KeeperTestSuite struct { + testutil.Suite +} + +func (suite *KeeperTestSuite) TestInitGenesis() { + tests := []struct { + name string + genesisState *types.GenesisState + shouldPanic bool + panicMsg string + }{ + { + "default genesisState", + types.DefaultGenesisState(), + false, + "", + }, + { + "empty genesisState", + &types.GenesisState{}, + false, + "", + }, + { + "TODO: invalid genesisState", + &types.GenesisState{}, + false, + "", + }, + } + + 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) + + return + } + + suite.Require().NotPanics(func() { + precisebank.InitGenesis(suite.Ctx, suite.Keeper, suite.AccountKeeper, tc.genesisState) + }) + + // Ensure module account is created + moduleAcc := suite.AccountKeeper.GetModuleAccount(suite.Ctx, types.ModuleName) + suite.NotNil(moduleAcc, "module account should be created") + + // TODO: Check module state once implemented + + // - Verify balances + // - Ensure reserve account exists + // - Ensure reserve balance matches sum of all fractional balances + // - etc + }) + } +} + +func (suite *KeeperTestSuite) TestExportGenesis_Valid() { + // ExportGenesis(moduleState) should return a valid genesis state + + tests := []struct { + name string + maleate func() + }{ + { + "InitGenesis(DefaultGenesisState)", + func() { + precisebank.InitGenesis( + suite.Ctx, + suite.Keeper, + suite.AccountKeeper, + types.DefaultGenesisState(), + ) + }, + }, + } + + for _, tc := range tests { + suite.Run(tc.name, func() { + tc.maleate() + + genesisState := precisebank.ExportGenesis(suite.Ctx, suite.Keeper) + + suite.Require().NoError(genesisState.Validate(), "exported genesis state should be valid") + }) + } +} + +func (suite *KeeperTestSuite) TestExportImportedState() { + // ExportGenesis(InitGenesis(genesisState)) == genesisState + + tests := []struct { + name string + initGenesisState *types.GenesisState + }{ + { + "InitGenesis(DefaultGenesisState)", + types.DefaultGenesisState(), + }, + } + + for _, tc := range tests { + suite.Run(tc.name, func() { + suite.Require().NotPanics(func() { + precisebank.InitGenesis( + suite.Ctx, + suite.Keeper, + suite.AccountKeeper, + tc.initGenesisState, + ) + }) + + genesisState := precisebank.ExportGenesis(suite.Ctx, suite.Keeper) + suite.Require().NoError(genesisState.Validate(), "exported genesis state should be valid") + + suite.Require().Equal( + tc.initGenesisState, + genesisState, + "exported genesis state should equal initial genesis state", + ) + }) + } +} diff --git a/x/precisebank/keeper/invariants.go b/x/precisebank/keeper/invariants.go new file mode 100644 index 00000000..8d4591d9 --- /dev/null +++ b/x/precisebank/keeper/invariants.go @@ -0,0 +1,21 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/precisebank/types" +) + +// RegisterInvariants registers the x/precisebank module invariants +func RegisterInvariants( + ir sdk.InvariantRegistry, + k Keeper, + bk types.BankKeeper, +) { +} + +// AllInvariants runs all invariants of the X/precisebank module. +func AllInvariants(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + return "", false + } +} diff --git a/x/precisebank/keeper/keeper.go b/x/precisebank/keeper/keeper.go new file mode 100644 index 00000000..df39e747 --- /dev/null +++ b/x/precisebank/keeper/keeper.go @@ -0,0 +1,23 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" +) + +// TODO: Enforce that Keeper implements the expected keeper interfaces +// var _ types.BankKeeper = Keeper{} + +// Keeper defines the precisebank module's keeper +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey +} + +// NewKeeper creates a new keeper +func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey) Keeper { + return Keeper{ + cdc: cdc, + storeKey: storeKey, + } +} diff --git a/x/precisebank/module.go b/x/precisebank/module.go new file mode 100644 index 00000000..00921fe8 --- /dev/null +++ b/x/precisebank/module.go @@ -0,0 +1,154 @@ +package precisebank + +import ( + "encoding/json" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + abci "github.com/cometbft/cometbft/abci/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/kava-labs/kava/x/precisebank/keeper" + "github.com/kava-labs/kava/x/precisebank/types" +) + +// ConsensusVersion defines the current module consensus version. +const ConsensusVersion = 1 + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic app module basics object +type AppModuleBasic struct{} + +func NewAppModuleBasic() AppModuleBasic { + return AppModuleBasic{} +} + +// Name get module name +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// Registers legacy amino codec +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers the module's interface types +func (a AppModuleBasic) RegisterInterfaces(reg cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(reg) +} + +// DefaultGenesis default genesis state +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + gs := types.DefaultGenesisState() + return cdc.MustMarshalJSON(gs) +} + +// ValidateGenesis module validate genesis +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + err := cdc.UnmarshalJSON(bz, &gs) + if err != nil { + return err + } + return gs.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for precisebank module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { +} + +// GetTxCmd returns precisebank module's root tx command. +func (a AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd returns precisebank module's root query command. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements the AppModule interface for precisebank module. +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule( + keeper keeper.Keeper, + bankKeeper types.BankKeeper, + accountKeeper types.AccountKeeper, +) AppModule { + return AppModule{ + AppModuleBasic: NewAppModuleBasic(), + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// Name returns precisebank module's name. +func (am AppModule) Name() string { + return am.AppModuleBasic.Name() +} + +// RegisterServices registers a GRPC query service to respond to the +// module-specific GRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { +} + +// RegisterInvariants registers precisebank module's invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + keeper.RegisterInvariants(ir, am.keeper, am.bankKeeper) +} + +// InitGenesis performs precisebank module's genesis initialization It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + // Initialize global index to index in genesis state + cdc.MustUnmarshalJSON(gs, &genState) + + InitGenesis(ctx, am.keeper, am.accountKeeper, &genState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns precisebank module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +// BeginBlock executes all ABCI BeginBlock logic respective to precisebank module. +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock executes all ABCI EndBlock logic respective to precisebank module. It +// returns no validator updates. +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/precisebank/testutil/suite.go b/x/precisebank/testutil/suite.go new file mode 100644 index 00000000..3b64e769 --- /dev/null +++ b/x/precisebank/testutil/suite.go @@ -0,0 +1,89 @@ +package testutil + +import ( + "time" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cometbft/cometbft/crypto/tmhash" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmversion "github.com/cometbft/cometbft/proto/tendermint/version" + tmtime "github.com/cometbft/cometbft/types/time" + "github.com/cometbft/cometbft/version" + sdk "github.com/cosmos/cosmos-sdk/types" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + "github.com/evmos/ethermint/crypto/ethsecp256k1" + "github.com/stretchr/testify/suite" + + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/precisebank/keeper" +) + +type Suite struct { + suite.Suite + + App app.TestApp + Ctx sdk.Context + BankKeeper bankkeeper.Keeper + AccountKeeper authkeeper.AccountKeeper + Keeper keeper.Keeper +} + +func (suite *Suite) SetupTest() { + tApp := app.NewTestApp() + + suite.Ctx = tApp.NewContext(true, tmproto.Header{Height: 1, Time: tmtime.Now()}) + suite.App = tApp + suite.BankKeeper = tApp.GetBankKeeper() + suite.AccountKeeper = tApp.GetAccountKeeper() + suite.Keeper = tApp.GetPrecisebankKeeper() + + cdc := suite.App.AppCodec() + coins := sdk.NewCoins(sdk.NewInt64Coin("ukava", 1000_000_000_000_000_000)) + authGS := app.NewFundedGenStateWithSameCoins(cdc, coins, []sdk.AccAddress{}) + + gs := app.GenesisState{} + suite.App.InitializeFromGenesisStates(authGS, gs) + + // consensus key - needed to set up evm module + consPriv, err := ethsecp256k1.GenerateKey() + suite.Require().NoError(err) + consAddress := sdk.ConsAddress(consPriv.PubKey().Address()) + + // InitializeFromGenesisStates commits first block so we start at 2 here + suite.Ctx = suite.App.NewContext(false, tmproto.Header{ + Height: suite.App.LastBlockHeight() + 1, + ChainID: app.TestChainId, + Time: time.Now().UTC(), + ProposerAddress: consAddress.Bytes(), + Version: tmversion.Consensus{ + Block: version.BlockProtocol, + }, + LastBlockId: tmproto.BlockID{ + Hash: tmhash.Sum([]byte("block_id")), + PartSetHeader: tmproto.PartSetHeader{ + Total: 11, + Hash: tmhash.Sum([]byte("partset_header")), + }, + }, + AppHash: tmhash.Sum([]byte("app")), + DataHash: tmhash.Sum([]byte("data")), + EvidenceHash: tmhash.Sum([]byte("evidence")), + ValidatorsHash: tmhash.Sum([]byte("validators")), + NextValidatorsHash: tmhash.Sum([]byte("next_validators")), + ConsensusHash: tmhash.Sum([]byte("consensus")), + LastResultsHash: tmhash.Sum([]byte("last_result")), + }) +} + +func (suite *Suite) Commit() { + _ = suite.App.Commit() + header := suite.Ctx.BlockHeader() + header.Height += 1 + suite.App.BeginBlock(abci.RequestBeginBlock{ + Header: header, + }) + + // update ctx + suite.Ctx = suite.App.NewContext(false, header) +} diff --git a/x/precisebank/types/codec.go b/x/precisebank/types/codec.go new file mode 100644 index 00000000..772fa40e --- /dev/null +++ b/x/precisebank/types/codec.go @@ -0,0 +1,33 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authzcodec "github.com/cosmos/cosmos-sdk/x/authz/codec" +) + +// RegisterLegacyAminoCodec registers the necessary evmutil interfaces and concrete types +// on the provided LegacyAmino codec. These types are used for Amino JSON serialization. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { +} + +func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil)) +} + +var ( + amino = codec.NewLegacyAmino() + ModuleCdc = codec.NewAminoCodec(amino) +) + +func init() { + RegisterLegacyAminoCodec(amino) + cryptocodec.RegisterCrypto(amino) + amino.Seal() + + // Register all Amino interfaces and concrete types on the authz Amino codec so that this can later be + // used to properly serialize MsgGrant and MsgExec instances + RegisterLegacyAminoCodec(authzcodec.Amino) +} diff --git a/x/precisebank/types/expected_keepers.go b/x/precisebank/types/expected_keepers.go new file mode 100644 index 00000000..43d2415b --- /dev/null +++ b/x/precisebank/types/expected_keepers.go @@ -0,0 +1,23 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + evmtypes "github.com/evmos/ethermint/x/evm/types" +) + +// AccountKeeper defines the expected account keeper interface +type AccountKeeper interface { + GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI + GetModuleAddress(moduleName string) sdk.AccAddress + GetSequence(sdk.Context, sdk.AccAddress) (uint64, error) +} + +// BankKeeper defines the expected bank keeper interface +type BankKeeper interface { + evmtypes.BankKeeper + + GetSupply(ctx sdk.Context, denom string) sdk.Coin + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error +} diff --git a/x/precisebank/types/genesis.go b/x/precisebank/types/genesis.go new file mode 100644 index 00000000..aa301b33 --- /dev/null +++ b/x/precisebank/types/genesis.go @@ -0,0 +1,17 @@ +package types + +// Validate performs basic validation of supply genesis data returning an +// error for any failed validation criteria. +func (gs *GenesisState) Validate() error { + return nil +} + +// NewGenesisState creates a new genesis state. +func NewGenesisState() *GenesisState { + return &GenesisState{} +} + +// DefaultGenesisState returns a default genesis state. +func DefaultGenesisState() *GenesisState { + return NewGenesisState() +} diff --git a/x/precisebank/types/genesis.pb.go b/x/precisebank/types/genesis.pb.go new file mode 100644 index 00000000..d5d2519b --- /dev/null +++ b/x/precisebank/types/genesis.pb.go @@ -0,0 +1,264 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: kava/precisebank/v1/genesis.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState defines the precisebank module's genesis state. +type GenesisState struct { +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_7f1c47a86fb0d2e0, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GenesisState)(nil), "kava.precisebank.v1.GenesisState") +} + +func init() { proto.RegisterFile("kava/precisebank/v1/genesis.proto", fileDescriptor_7f1c47a86fb0d2e0) } + +var fileDescriptor_7f1c47a86fb0d2e0 = []byte{ + // 145 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0xcc, 0x4e, 0x2c, 0x4b, + 0xd4, 0x2f, 0x28, 0x4a, 0x4d, 0xce, 0x2c, 0x4e, 0x4d, 0x4a, 0xcc, 0xcb, 0xd6, 0x2f, 0x33, 0xd4, + 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x06, + 0x29, 0xd1, 0x43, 0x52, 0xa2, 0x57, 0x66, 0xa8, 0xc4, 0xc7, 0xc5, 0xe3, 0x0e, 0x51, 0x15, 0x5c, + 0x92, 0x58, 0x92, 0xea, 0xe4, 0x7e, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, + 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, + 0xba, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0xfa, 0x20, 0x93, 0x74, 0x73, + 0x12, 0x93, 0x8a, 0xc1, 0x2c, 0xfd, 0x0a, 0x14, 0x8b, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, + 0xc0, 0x96, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x51, 0x07, 0x09, 0x29, 0x99, 0x00, 0x00, + 0x00, +} + +func (m *GenesisState) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/precisebank/types/genesis_test.go b/x/precisebank/types/genesis_test.go new file mode 100644 index 00000000..bd49a2fd --- /dev/null +++ b/x/precisebank/types/genesis_test.go @@ -0,0 +1,47 @@ +package types_test + +import ( + "testing" + + "github.com/kava-labs/kava/x/precisebank/types" + "github.com/stretchr/testify/require" +) + +func TestGenesisStateValidate(t *testing.T) { + testCases := []struct { + name string + genesisState types.GenesisState + expErr bool + }{ + { + "empty genesisState", + types.GenesisState{}, + false, + }, + { + "valid genesisState", + // TODO: Fill out fields + types.GenesisState{}, + false, + }, + // TODO: Sum of balances does not equal an integer amount + // { + // "invalid balances", + // types.GenesisState{}, + // true, + // }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(tt *testing.T) { + err := tc.genesisState.Validate() + + if tc.expErr { + require.Error(tt, err) + } else { + require.NoError(tt, err) + } + }) + } +} diff --git a/x/precisebank/types/keys.go b/x/precisebank/types/keys.go new file mode 100644 index 00000000..58350118 --- /dev/null +++ b/x/precisebank/types/keys.go @@ -0,0 +1,11 @@ +package types + +const ( + // ModuleName name that will be used throughout the module + ModuleName = "precisebank" + + StoreKey = ModuleName + + // RouterKey Top level router key + RouterKey = ModuleName +)