diff --git a/go.mod b/go.mod index 21e89a2..d2a35c1 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( cosmossdk.io/core v0.11.0 cosmossdk.io/depinject v1.0.0-alpha.4 cosmossdk.io/log v1.2.1 + cosmossdk.io/math v1.1.3-rc.1 github.com/client9/misspell v0.3.4 github.com/cometbft/cometbft v0.37.2 github.com/cometbft/cometbft-db v0.8.0 @@ -35,7 +36,6 @@ require ( cloud.google.com/go/iam v1.1.4 // indirect cloud.google.com/go/storage v1.30.1 // indirect cosmossdk.io/errors v1.0.0 // indirect - cosmossdk.io/math v1.1.3-rc.1 // indirect cosmossdk.io/tools/rosetta v0.2.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/4meepo/tagalign v1.3.3 // indirect diff --git a/x/feemarket/keeper/abci.go b/x/feemarket/keeper/abci.go index 4b6e0ba..6711f72 100644 --- a/x/feemarket/keeper/abci.go +++ b/x/feemarket/keeper/abci.go @@ -5,12 +5,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// BeginBlock returns a beginblocker for the x/feemarket module. -func (k *Keeper) BeginBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { - return []abci.ValidatorUpdate{}, nil -} - -// EndBlock returns an endblocker for the x/feemarket module. +// EndBlock returns an endblocker for the x/feemarket module. The endblocker +// is responsible for updating the state of the fee market based on the +// AIMD learning rate adjustment algorithm. func (k *Keeper) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + if err := k.UpdateFeeMarket(ctx); err != nil { + panic(err) + } + return []abci.ValidatorUpdate{} } diff --git a/x/feemarket/keeper/feemarket.go b/x/feemarket/keeper/feemarket.go index e5c24e6..e973736 100644 --- a/x/feemarket/keeper/feemarket.go +++ b/x/feemarket/keeper/feemarket.go @@ -1,36 +1,16 @@ package keeper import ( - "encoding/json" - + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) -// ------------------- Fee Market Updates ------------------- // - -// Init initializes the fee market (in InitGenesis). -func (k *Keeper) Init(_ sdk.Context) error { - // TODO initialize fee market state with params - - return nil -} - -// Export exports the fee market (in ExportGenesis). -func (k *Keeper) Export(_ sdk.Context) (json.RawMessage, error) { - // TODO export state from fee market state - - return nil, nil -} - -// BeginBlocker allows the fee market to be updated -// after every block. This will be added to the BeginBlock chain. -func (k *Keeper) BeginBlocker(_ sdk.Context) error { - return nil -} - -// EndBlocker allows the fee market to be updated -// after every block. This will be added to the EndBlock chain. -func (k *Keeper) EndBlocker(ctx sdk.Context) error { +// UpdateFeeMarket updates the base fee and learning rate based on the +// AIMD learning rate adjustment algorithm. Note that if the fee market +// is disabled, this function will return without updating the fee market. +// This is executed in EndBlock which allows the next block's base fee to +// be readily available for wallets to estimate gas prices. +func (k *Keeper) UpdateFeeMarket(ctx sdk.Context) error { params, err := k.GetParams(ctx) if err != nil { return err @@ -71,3 +51,23 @@ func (k *Keeper) EndBlocker(ctx sdk.Context) error { state.IncrementHeight() return k.SetState(ctx, state) } + +// GetBaseFee returns the base fee from the fee market state. +func (k *Keeper) GetBaseFee(ctx sdk.Context) (math.Int, error) { + state, err := k.GetState(ctx) + if err != nil { + return math.Int{}, err + } + + return state.BaseFee, nil +} + +// GetLearningRate returns the learning rate from the fee market state. +func (k *Keeper) GetLearningRate(ctx sdk.Context) (math.LegacyDec, error) { + state, err := k.GetState(ctx) + if err != nil { + return math.LegacyDec{}, err + } + + return state.LearningRate, nil +} diff --git a/x/feemarket/keeper/feemarket_test.go b/x/feemarket/keeper/feemarket_test.go new file mode 100644 index 0000000..5fc0298 --- /dev/null +++ b/x/feemarket/keeper/feemarket_test.go @@ -0,0 +1,49 @@ +package keeper_test + +import ( + "github.com/skip-mev/feemarket/x/feemarket/types" +) + +func (s *KeeperTestSuite) TestUpdateFeeMarket() { + // TODO: add tests. +} + +func (s *KeeperTestSuite) TestGetBaseFee() { + s.Run("can retrieve base fee with default eip-1559", func() { + gs := types.DefaultGenesisState() + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + + fee, err := s.feemarketKeeper.GetBaseFee(s.ctx) + s.Require().NoError(err) + s.Require().Equal(fee, gs.State.BaseFee) + }) + + s.Run("can retrieve base fee with aimd eip-1559", func() { + gs := types.DefaultAIMDGenesisState() + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + + fee, err := s.feemarketKeeper.GetBaseFee(s.ctx) + s.Require().NoError(err) + s.Require().Equal(fee, gs.State.BaseFee) + }) +} + +func (s *KeeperTestSuite) TestGetLearningRate() { + s.Run("can retrieve learning rate with default eip-1559", func() { + gs := types.DefaultGenesisState() + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + + lr, err := s.feemarketKeeper.GetLearningRate(s.ctx) + s.Require().NoError(err) + s.Require().Equal(lr, gs.State.LearningRate) + }) + + s.Run("can retrieve learning rate with aimd eip-1559", func() { + gs := types.DefaultAIMDGenesisState() + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + + lr, err := s.feemarketKeeper.GetLearningRate(s.ctx) + s.Require().NoError(err) + s.Require().Equal(lr, gs.State.LearningRate) + }) +} diff --git a/x/feemarket/keeper/genesis.go b/x/feemarket/keeper/genesis.go index 62d1f6c..453f479 100644 --- a/x/feemarket/keeper/genesis.go +++ b/x/feemarket/keeper/genesis.go @@ -8,16 +8,29 @@ import ( // InitGenesis initializes the feemarket module's state from a given genesis state. func (k *Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) { - if err := gs.Params.ValidateBasic(); err != nil { + if err := gs.ValidateBasic(); err != nil { panic(err) } - // Set the feemarket module's parameters. + // Ensure that state and fee market match required configurations. + if gs.Params.MaxBlockUtilization != gs.State.MaxBlockUtilization { + panic("genesis state and parameters do not match for max block utilization") + } + + if gs.Params.TargetBlockUtilization != gs.State.TargetBlockUtilization { + panic("genesis state and parameters do not match for target block utilization") + } + + if gs.Params.Window != uint64(len(gs.State.Window)) { + panic("genesis state and parameters do not match for window") + } + + // Initialize the fee market state and parameters. if err := k.SetParams(ctx, gs.Params); err != nil { panic(err) } - if err := k.Init(ctx); err != nil { + if err := k.SetState(ctx, gs.State); err != nil { panic(err) } } @@ -30,10 +43,11 @@ func (k *Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { panic(err) } - // TODO get state - - state := types.DefaultGenesisState() - state.Params = params + // Get the feemarket module's state. + state, err := k.GetState(ctx) + if err != nil { + panic(err) + } - return state // TODO Return full state + return types.NewGenesisState(params, state) } diff --git a/x/feemarket/keeper/genesis_test.go b/x/feemarket/keeper/genesis_test.go index b61de1c..31434fe 100644 --- a/x/feemarket/keeper/genesis_test.go +++ b/x/feemarket/keeper/genesis_test.go @@ -11,5 +11,70 @@ func (s *KeeperTestSuite) TestInitGenesis() { }) }) - // TODO test further + s.Run("default AIMD genesis should not panic", func() { + s.Require().NotPanics(func() { + s.feemarketKeeper.InitGenesis(s.ctx, *types.DefaultAIMDGenesisState()) + }) + }) + + s.Run("bad genesis state should panic", func() { + gs := types.DefaultGenesisState() + gs.Params.Window = 0 + s.Require().Panics(func() { + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + }) + }) + + s.Run("mismatch in params and state for window should panic", func() { + gs := types.DefaultAIMDGenesisState() + gs.Params.Window = 1 + + s.Require().Panics(func() { + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + }) + }) + + s.Run("mismatch in params and state for target utilization should panic", func() { + gs := types.DefaultAIMDGenesisState() + gs.Params.TargetBlockUtilization = 1 + + s.Require().Panics(func() { + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + }) + }) + + s.Run("mismatch in params and state for max utilization should panic", func() { + gs := types.DefaultAIMDGenesisState() + gs.Params.MaxBlockUtilization = 1 + + s.Require().Panics(func() { + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + }) + }) +} + +func (s *KeeperTestSuite) TestExportGenesis() { + s.Run("export genesis should not panic for default eip-1559", func() { + gs := types.DefaultGenesisState() + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + + var exportedGenesis *types.GenesisState + s.Require().NotPanics(func() { + exportedGenesis = s.feemarketKeeper.ExportGenesis(s.ctx) + }) + + s.Require().Equal(gs, exportedGenesis) + }) + + s.Run("export genesis should not panic for default AIMD eip-1559", func() { + gs := types.DefaultAIMDGenesisState() + s.feemarketKeeper.InitGenesis(s.ctx, *gs) + + var exportedGenesis *types.GenesisState + s.Require().NotPanics(func() { + exportedGenesis = s.feemarketKeeper.ExportGenesis(s.ctx) + }) + + s.Require().Equal(gs, exportedGenesis) + }) } diff --git a/x/feemarket/module.go b/x/feemarket/module.go index ef50ef9..9080ae1 100644 --- a/x/feemarket/module.go +++ b/x/feemarket/module.go @@ -94,11 +94,6 @@ func NewAppModule(cdc codec.Codec, k keeper.Keeper) AppModule { } } -// BeginBlock returns a beginblocker for the x/feemarket module. -func (am AppModule) BeginBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { - return am.k.BeginBlock(ctx) -} - // EndBlock returns an endblocker for the x/feemarket module. func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate { return am.k.EndBlock(ctx, req)