diff --git a/go.mod b/go.mod index 62dec4da39ba..ca071a75bfef 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/cosmos/gogoproto v1.7.0 github.com/cosmos/ledger-cosmos-go v0.16.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 + github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.4 github.com/google/go-cmp v0.7.0 github.com/google/gofuzz v1.2.0 diff --git a/go.sum b/go.sum index bc3adc7b5107..823bd6b955bd 100644 --- a/go.sum +++ b/go.sum @@ -706,6 +706,7 @@ github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtX github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zondax/golem v0.27.0 h1:IbBjGIXF3SoGOZHsILJvIM/F/ylwJzMcHAcggiqniPw= github.com/zondax/golem v0.27.0/go.mod h1:AmorCgJPt00L8xN1VrMBe13PSifoZksnQ1Ge906bu4A= @@ -789,6 +790,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= @@ -837,6 +839,7 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= @@ -939,6 +942,7 @@ golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= diff --git a/proto/cosmos/dynamicfee/module/v1/module.proto b/proto/cosmos/dynamicfee/module/v1/module.proto new file mode 100644 index 000000000000..a7c0514c035f --- /dev/null +++ b/proto/cosmos/dynamicfee/module/v1/module.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package cosmos.dynamicfee.module.v1; + +import "cosmos/app/v1alpha1/module.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/dynamicfee/types/module"; + +// Module is the config object of the builder module. +message Module { + option (cosmos.app.v1alpha1.module) = { + go_import: "github.com/cosmos/cosmos-sdk/x/dynamicfee" + }; + + // Authority defines the custom module authority. If not set, defaults to the + // governance module. + string authority = 1; +} diff --git a/proto/cosmos/dynamicfee/v1/genesis.proto b/proto/cosmos/dynamicfee/v1/genesis.proto new file mode 100644 index 000000000000..b1feb72a0a5b --- /dev/null +++ b/proto/cosmos/dynamicfee/v1/genesis.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; +package cosmos.dynamicfee.v1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/dynamicfee/types"; + +import "gogoproto/gogo.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/dynamicfee/v1/params.proto"; + +// GenesisState defines the dynamicfee module's genesis state. +message GenesisState { + // Params are the parameters for the dynamicfee module. These parameters + // can be utilized to implement both the base EIP-1559 dynamic fee pricing + // and the AIMD EIP-1559 dynamic fee pricing. + Params params = 1 [(gogoproto.nullable) = false]; + + // State contains the current state of the AIMD dynamic fee pricer. + State state = 2 [(gogoproto.nullable) = false]; +} + +// State is utilized to track the current state of the dynamic fee pricer. +// This includes the current base fee, learning rate, and block gas within the +// specified AIMD window. +message State { + // BaseGasPrice is the current base fee. This is denominated in the fee per + // gas unit. + string base_gas_price = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // LearningRate is the current learning rate. + string learning_rate = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Window contains a list of the last blocks' gas values. This is used + // to calculate the next base fee. This stores the number of units of gas + // consumed per block. + repeated uint64 window = 3; + + // Index is the index of the current block in the block gas window. + uint64 index = 4; +} diff --git a/proto/cosmos/dynamicfee/v1/params.proto b/proto/cosmos/dynamicfee/v1/params.proto new file mode 100644 index 000000000000..b273df2429c2 --- /dev/null +++ b/proto/cosmos/dynamicfee/v1/params.proto @@ -0,0 +1,90 @@ +syntax = "proto3"; +package cosmos.dynamicfee.v1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/dynamicfee/types"; + +import "cosmos_proto/cosmos.proto"; +import "gogoproto/gogo.proto"; + +// Params contains the required set of parameters for the EIP1559 dynamic fee +// pricing implementation. +message Params { + // Alpha is the amount we additively increase the learning rate + // when it is above or below the target +/- threshold. + // + // Must be > 0. + string alpha = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Beta is the amount we multiplicatively decrease the learning rate + // when it is within the target +/- threshold. + // + // Must be [0, 1]. + string beta = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Gamma is the threshold for the learning rate. If the learning rate is + // above or below the target +/- threshold, we additively increase the + // learning rate by Alpha. Otherwise, we multiplicatively decrease the + // learning rate by Beta. + // + // Must be [0, 0.5]. + string gamma = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // MinBaseGasPrice determines the initial gas price of the module and the + // global minimum for the network. + string min_base_gas_price = 5 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // TargetBlockUtilization is the target block utilization expressed as a + // decimal value between 0 and 1. It is the target percentage utilization + // of the block in relation to the consensus_params.block.max_gas parameter. + string target_block_utilization = 6 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // DefaultMaxBlockGas is the default max block gas. + // This parameter is used by the dynamicfee module + // in the case consensus_params.block.max_gas returns 0 or -1. + uint64 default_max_block_gas = 7; + + // MinLearningRate is the lower bound for the learning rate. + string min_learning_rate = 8 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // MaxLearningRate is the upper bound for the learning rate. + string max_learning_rate = 9 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Window defines the window size for calculating an adaptive learning rate + // over a moving window of blocks. + uint64 window = 10; + + // FeeDenom is the denom that will be used for all fee payments. + string fee_denom = 11; + + // Enabled is a boolean that determines whether the EIP1559 dynamic fee + // pricing is enabled. + bool enabled = 12; +} diff --git a/proto/cosmos/dynamicfee/v1/query.proto b/proto/cosmos/dynamicfee/v1/query.proto new file mode 100644 index 000000000000..92f243203f1a --- /dev/null +++ b/proto/cosmos/dynamicfee/v1/query.proto @@ -0,0 +1,85 @@ +syntax = "proto3"; +package cosmos.dynamicfee.v1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/dynamicfee/types"; + +import "gogoproto/gogo.proto"; +import "google/api/annotations.proto"; +import "cosmos/base/v1beta1/coin.proto"; +import "amino/amino.proto"; +import "cosmos/dynamicfee/v1/params.proto"; +import "cosmos/dynamicfee/v1/genesis.proto"; + +// Query Service for the dynamicfee module. +service Query { + // Params returns the current dynamicfee module parameters. + rpc Params(ParamsRequest) returns (ParamsResponse) { + option (google.api.http) = { + get: "/cosmos/dynamicfee/v1/params" + }; + }; + + // State returns the current dynamicfee module state. + rpc State(StateRequest) returns (StateResponse) { + option (google.api.http) = { + get: "/cosmos/dynamicfee/v1/state" + }; + }; + + // GasPrice returns the current dynamicfee module gas price + // for specified denom. + rpc GasPrice(GasPriceRequest) returns (GasPriceResponse) { + option (google.api.http) = { + get: "/cosmos/dynamicfee/v1/gas_price/{denom}" + }; + }; + + // GasPrices returns the current dynamicfee module list of gas prices + // in all available denoms. + rpc GasPrices(GasPricesRequest) returns (GasPricesResponse) { + option (google.api.http) = { + get: "/cosmos/dynamicfee/v1/gas_prices" + }; + }; +} + +// ParamsRequest is the request type for the Query/Params RPC method. +message ParamsRequest {} + +// ParamsResponse is the response type for the Query/Params RPC method. +message ParamsResponse { + Params params = 1 [(gogoproto.nullable) = false]; +} + +// StateRequest is the request type for the Query/State RPC method. +message StateRequest {} + +// StateResponse is the response type for the Query/State RPC method. +message StateResponse { + State state = 1 [(gogoproto.nullable) = false]; +} + +// GasPriceRequest is the request type for the Query/GasPrice RPC method. +message GasPriceRequest { + // denom we are querying gas price in + string denom = 1; +} + +// GasPriceResponse is the response type for the Query/GasPrice RPC method. +// Returns a gas price in specified denom. +message GasPriceResponse { + cosmos.base.v1beta1.DecCoin price = 1 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true]; +} + +// GasPriceRequest is the request type for the Query/GasPrices RPC method. +message GasPricesRequest {} + +// GasPricesResponse is the response type for the Query/GasPrices RPC method. +// Returns a gas price in all available denoms. +message GasPricesResponse { + repeated cosmos.base.v1beta1.DecCoin prices = 1 [ + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins" + ]; +} diff --git a/proto/cosmos/dynamicfee/v1/tx.proto b/proto/cosmos/dynamicfee/v1/tx.proto new file mode 100644 index 000000000000..93ed4edf7b7d --- /dev/null +++ b/proto/cosmos/dynamicfee/v1/tx.proto @@ -0,0 +1,36 @@ +syntax = "proto3"; +package cosmos.dynamicfee.v1; + +import "cosmos/dynamicfee/v1/params.proto"; +import "cosmos_proto/cosmos.proto"; +import "cosmos/msg/v1/msg.proto"; +import "gogoproto/gogo.proto"; +import "amino/amino.proto"; + +option go_package = "github.com/cosmos/cosmos-sdk/x/dynamicfee/types"; + +// Message service defines the types of messages supported by the dynamicfee +// module. +service Msg { + option (cosmos.msg.v1.service) = true; + + // UpdateParams defines a method for updating the dynamicfee module parameters. + rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse); +} + +// MsgUpdateParams defines the sdk.Msg/UpdateParams request type. It contains +// the new parameters for the dynamicfee module. +message MsgUpdateParams { + option (cosmos.msg.v1.signer) = "authority"; + option (amino.name) = "cosmos/x/dynamicfee/v1/MsgUpdateParams"; + + // Authority defines the authority that is updating the dynamicfee module + // parameters. + string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; + // Params defines the new parameters for the dynamicfee module. + Params params = 2 [(gogoproto.nullable) = false]; +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +message MsgUpdateParamsResponse {} diff --git a/scripts/mockgen.sh b/scripts/mockgen.sh index 142f1f1eb077..4fa7076cee73 100755 --- a/scripts/mockgen.sh +++ b/scripts/mockgen.sh @@ -22,3 +22,5 @@ $mockgen_cmd -source=x/genutil/types/expected_keepers.go -package testutil -dest $mockgen_cmd -source=x/gov/testutil/expected_keepers.go -package testutil -destination x/gov/testutil/expected_keepers_mocks.go $mockgen_cmd -source=x/staking/types/expected_keepers.go -package testutil -destination x/staking/testutil/expected_keepers_mocks.go $mockgen_cmd -source=x/auth/vesting/types/expected_keepers.go -package testutil -destination x/auth/vesting/testutil/expected_keepers_mocks.go +$mockgen_cmd -source=x/dynamicfee/ante/expected_keepers.go -package ante_test -destination x/dynamicfee/ante/expected_keepers_mocks_test.go +$mockgen_cmd -source=x/dynamicfee/post/expected_keepers.go -package post_test -destination x/dynamicfee/post/expected_keepers_mocks_test.go diff --git a/simapp/app_config.go b/simapp/app_config.go index ae6e20d43dac..625a4b138cd7 100644 --- a/simapp/app_config.go +++ b/simapp/app_config.go @@ -39,6 +39,9 @@ import ( _ "github.com/cosmos/cosmos-sdk/x/distribution" // import for side-effects distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" distrmodulev1 "github.com/cosmos/cosmos-sdk/x/distribution/types/module" + _ "github.com/cosmos/cosmos-sdk/x/dynamicfee" // import for side-effects + dynamicfeetypes "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" + dynamicfeemodulev1 "github.com/cosmos/cosmos-sdk/x/dynamicfee/types/module" _ "github.com/cosmos/cosmos-sdk/x/epochs" // import for side-effects epochstypes "github.com/cosmos/cosmos-sdk/x/epochs/types" epochsmodulev1 "github.com/cosmos/cosmos-sdk/x/epochs/types/module" @@ -73,6 +76,7 @@ var ( {Account: stakingtypes.BondedPoolName, Permissions: []string{authtypes.Burner, stakingtypes.ModuleName}}, {Account: stakingtypes.NotBondedPoolName, Permissions: []string{authtypes.Burner, stakingtypes.ModuleName}}, {Account: govtypes.ModuleName, Permissions: []string{authtypes.Burner}}, + {Account: dynamicfeetypes.ModuleName}, } // blocked account addresses @@ -103,6 +107,7 @@ var ( // NOTE: staking module is required if HistoricalEntries param > 0 BeginBlockers: []string{ minttypes.ModuleName, + dynamicfeetypes.ModuleName, distrtypes.ModuleName, slashingtypes.ModuleName, evidencetypes.ModuleName, @@ -111,6 +116,7 @@ var ( epochstypes.ModuleName, }, EndBlockers: []string{ + dynamicfeetypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName, feegrant.ModuleName, @@ -141,6 +147,7 @@ var ( vestingtypes.ModuleName, circuittypes.ModuleName, epochstypes.ModuleName, + dynamicfeetypes.ModuleName, }, // When ExportGenesis is not specified, the export genesis module order // is equal to the init genesis order @@ -236,6 +243,10 @@ var ( Name: epochstypes.ModuleName, Config: appconfig.WrapAny(&epochsmodulev1.Module{}), }, + { + Name: dynamicfeetypes.ModuleName, + Config: appconfig.WrapAny(&dynamicfeemodulev1.Module{}), + }, }, }), depinject.Supply( diff --git a/simapp/app_di.go b/simapp/app_di.go index 9113a6d000db..2e0ed28c4841 100644 --- a/simapp/app_di.go +++ b/simapp/app_di.go @@ -34,6 +34,7 @@ import ( bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" consensuskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper" distrkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + dynamicfeekeeper "github.com/cosmos/cosmos-sdk/x/dynamicfee/keeper" epochskeeper "github.com/cosmos/cosmos-sdk/x/epochs/keeper" govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper" mintkeeper "github.com/cosmos/cosmos-sdk/x/mint/keeper" @@ -77,6 +78,7 @@ type SimApp struct { ConsensusParamsKeeper consensuskeeper.Keeper CircuitKeeper circuitkeeper.Keeper EpochsKeeper *epochskeeper.Keeper + DynamicfeeKeeper dynamicfeekeeper.Keeper // simulation manager sm *module.SimulationManager @@ -176,6 +178,7 @@ func NewSimApp( &app.ConsensusParamsKeeper, &app.CircuitKeeper, &app.EpochsKeeper, + &app.DynamicfeeKeeper, ); err != nil { panic(err) } diff --git a/simapp/app_test.go b/simapp/app_test.go index 8498949f1e73..693c7dcc9aad 100644 --- a/simapp/app_test.go +++ b/simapp/app_test.go @@ -41,6 +41,8 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/distribution" disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee" + dynamicfeetypes "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" "github.com/cosmos/cosmos-sdk/x/epochs" epochstypes "github.com/cosmos/cosmos-sdk/x/epochs/types" "github.com/cosmos/cosmos-sdk/x/genutil" @@ -208,21 +210,22 @@ func TestRunMigrations(t *testing.T) { _, err = app.ModuleManager.RunMigrations( app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}), configurator, module.VersionMap{ - banktypes.ModuleName: 1, - authtypes.ModuleName: auth.AppModule{}.ConsensusVersion(), - authz.ModuleName: authzmodule.AppModule{}.ConsensusVersion(), - stakingtypes.ModuleName: staking.AppModule{}.ConsensusVersion(), - minttypes.ModuleName: mint.AppModule{}.ConsensusVersion(), - disttypes.ModuleName: distribution.AppModule{}.ConsensusVersion(), - slashingtypes.ModuleName: slashing.AppModule{}.ConsensusVersion(), - govtypes.ModuleName: gov.AppModule{}.ConsensusVersion(), - paramstypes.ModuleName: params.AppModule{}.ConsensusVersion(), - upgradetypes.ModuleName: upgrade.AppModule{}.ConsensusVersion(), - vestingtypes.ModuleName: vesting.AppModule{}.ConsensusVersion(), - feegrant.ModuleName: feegrantmodule.AppModule{}.ConsensusVersion(), - evidencetypes.ModuleName: evidence.AppModule{}.ConsensusVersion(), - genutiltypes.ModuleName: genutil.AppModule{}.ConsensusVersion(), - epochstypes.ModuleName: epochs.AppModule{}.ConsensusVersion(), + banktypes.ModuleName: 1, + authtypes.ModuleName: auth.AppModule{}.ConsensusVersion(), + authz.ModuleName: authzmodule.AppModule{}.ConsensusVersion(), + stakingtypes.ModuleName: staking.AppModule{}.ConsensusVersion(), + minttypes.ModuleName: mint.AppModule{}.ConsensusVersion(), + disttypes.ModuleName: distribution.AppModule{}.ConsensusVersion(), + slashingtypes.ModuleName: slashing.AppModule{}.ConsensusVersion(), + govtypes.ModuleName: gov.AppModule{}.ConsensusVersion(), + paramstypes.ModuleName: params.AppModule{}.ConsensusVersion(), + upgradetypes.ModuleName: upgrade.AppModule{}.ConsensusVersion(), + vestingtypes.ModuleName: vesting.AppModule{}.ConsensusVersion(), + feegrant.ModuleName: feegrantmodule.AppModule{}.ConsensusVersion(), + evidencetypes.ModuleName: evidence.AppModule{}.ConsensusVersion(), + genutiltypes.ModuleName: genutil.AppModule{}.ConsensusVersion(), + epochstypes.ModuleName: epochs.AppModule{}.ConsensusVersion(), + dynamicfeetypes.ModuleName: dynamicfee.AppModule{}.ConsensusVersion(), }, ) if tc.expRunErr { @@ -271,6 +274,7 @@ func TestInitGenesisOnMigration(t *testing.T) { "feegrant": feegrantmodule.AppModule{}.ConsensusVersion(), "evidence": evidence.AppModule{}.ConsensusVersion(), "genutil": genutil.AppModule{}.ConsensusVersion(), + "dynamicfee": dynamicfee.AppModule{}.ConsensusVersion(), }, ) require.NoError(t, err) diff --git a/testutil/configurator/configurator.go b/testutil/configurator/configurator.go index 2822e2a19e27..61b7744a150a 100644 --- a/testutil/configurator/configurator.go +++ b/testutil/configurator/configurator.go @@ -18,6 +18,7 @@ import ( bankmodulev1 "github.com/cosmos/cosmos-sdk/x/bank/types/module" consensusmodulev1 "github.com/cosmos/cosmos-sdk/x/consensus/types/module" distrmodulev1 "github.com/cosmos/cosmos-sdk/x/distribution/types/module" + dynamicfeemodulev1 "github.com/cosmos/cosmos-sdk/x/dynamicfee/types/module" genutilmodulev1 "github.com/cosmos/cosmos-sdk/x/genutil/types/module" govmodulev1 "github.com/cosmos/cosmos-sdk/x/gov/types/module" mintmodulev1 "github.com/cosmos/cosmos-sdk/x/mint/types/module" @@ -61,6 +62,7 @@ func defaultConfig() *Config { "consensus", "vesting", "circuit", + "dynamicfee", }, EndBlockersOrder: []string{ "crisis", @@ -82,6 +84,7 @@ func defaultConfig() *Config { "upgrade", "vesting", "circuit", + "dynamicfee", }, InitGenesisOrder: []string{ "auth", @@ -103,6 +106,7 @@ func defaultConfig() *Config { "upgrade", "vesting", "circuit", + "dynamicfee", }, setInitGenesis: true, } @@ -199,6 +203,15 @@ func SlashingModule() ModuleOption { } } +func DynamicfeeModule() ModuleOption { + return func(config *Config) { + config.ModuleConfigs["dynamicfee"] = &appv1alpha1.ModuleConfig{ + Name: "dynamicfee", + Config: appconfig.WrapAny(&dynamicfeemodulev1.Module{}), + } + } +} + func GenutilModule() ModuleOption { return func(config *Config) { config.ModuleConfigs["genutil"] = &appv1alpha1.ModuleConfig{ diff --git a/x/dynamicfee/AIMD.md b/x/dynamicfee/AIMD.md new file mode 100644 index 000000000000..795b042c9d1e --- /dev/null +++ b/x/dynamicfee/AIMD.md @@ -0,0 +1,163 @@ +# Additive Increase Multiplicative Decrease (AIMD) EIP-1559 + +## Overview + +> **Definitions:** +> +> * **`Target Block Gas`**: This is the target block gas consumption. +> * **`Max Block Gas`**: This is the maximum block gas consumption, fetched +> from the `x/consensus` module. + +This plugin implements the AIMD (Additive Increase Multiplicative Decrease) +EIP-1559 dynamic fee pricing as described in this +[AIMD EIP-1559](https://arxiv.org/abs/2110.04753) research publication. + +The AIMD EIP-1559 dynamic fee pricing is a slight modification to Ethereum's +EIP-1559 dynamic fee pricing. Specifically it introduces the notion of an +adaptive learning rate which scales the base gas price more aggressively when +the network is congested and less aggressively when the network is not +congested. This is primarily done to address the often cited criticism of +EIP-1559 that it's base fee often lags behind the current demand for block +space. The learning rate on Ethereum is effectively hard-coded to be 12.5%, +which means that between any two blocks the base fee can maximally increase by +12.5% or decrease by 12.5%. Additionally, AIMD EIP-1559 differs from Ethereum's +EIP-1559 by considering a configured time window (number of blocks) to consider +when calculating and comparing target block gas and current block gas. + +## Parameters + +### Ethereum EIP-1559 + +Base EIP-1559 currently utilizes the following parameters to compute the base fee: + +* **`PreviousBaseGasPrice`**: This is the base gas price from the previous + block. This must be a value that is greater than `0`. +* **`TargetBlockSize`**: This is the target block size in bytes. This must be a + value that is greater than `0`. +* **`PreviousBlockSize`**: This is the block size from the previous block. + +The calculation for the updated base fee for the next block is as follows: + +```golang +currentBaseGasPrice := previousBaseGasPrice * (1 + 0.125 * (currentBlockSize - targetBlockSize) / targetBlockSize) +``` + +### AIMD EIP-1559 + +AIMD EIP-1559 introduces a few new parameters to the EIP-1559 dynamic fee pricing: + +* **`Alpha`**: This is the amount we additively increase the learning rate when + the target gas is less than the current gas i.e. the block was + more full than the target gas. This must be a value that is greater than `0.0`. +* **`Beta`**: This is the amount we multiplicatively decrease the learning rate + when the target gas is greater than the current gas i.e. the + block was less full than the target gas. This must be a value that is greater + than `0.0`. +* **`Window`**: This is the number of blocks we look back to compute the current + gas. This must be a value that is greater than `0`. Instead of only + utilizing the previous block's gas, we now consider the gas of + the previous `Window` blocks. +* **`Gamma`**: This determines whether you are additively increase or + multiplicatively decreasing the learning rate based on the target and current + block gas. This must be a value that is between `[0, 1]`. For example, + if `Gamma = 0.25`, then we multiplicatively decrease the learning rate if the + average ratio of current block gas to max block gas over some window of + blocks is within `(0.25, 0.75)` and additively increase it if outside that range. +* **`MaxLearningRate`**: This is the maximum learning rate that can be applied + to the base fee. This must be a value that is between `[0, 1]`. +* **`MinLearningRate`**: This is the minimum learning rate that can be applied + to the base fee. This must be a value that is between `[0, 1]`. + +The calculation for the updated base fee for the next block is as follows: + +```golang + +// sumBlockGasInWindow returns the sum of the block gas in the window. +blockConsumption := sumBlockGasInWindow(window) / (window * maxBlockGas) + +if blockConsumption <= gamma || blockConsumption >= 1 - gamma { + // MAX_LEARNING_RATE is a constant that is configured by the chain developer + newLearningRate := min(MaxLearningRate, alpha + currentLearningRate) +} else { + // MIN_LEARNING_RATE is a constant that is configured by the chain developer + newLearningRate := max(MinLearningRate, beta * currentLearningRate) +} + +newBaseGasPrice := currentBaseGasPrice * (1 + newLearningRate * (currentBlockGas - targetBlockGas) / targetBlockGas) +``` + +The expected behavior is the following: when the current block gas is close to +the `targetBlockGas` (in other words, when `blockConsumption` is in the +`gamma` range), then the base gas price is close to the right value, so the +algorithm reduces the learning rate to reduce the size of the oscillations. By +contrast, if the current block gas is too small or too high +(`blockConsumption` is out of `gamma` range), then the base fee is apparently +far away from its equilibrium value, and the algorithm increases the learning +rate. + +## Examples + +> **Assume the following parameters:** +> +> * `TargetBlockGas = 50` +> * `MaxBlockGas = 100` +> * `Window = 1` +> * `Alpha = 0.025` +> * `Beta = 0.95` +> * `Gamma = 0.25` +> * `MAX_LEARNING_RATE = 1.0` +> * `MIN_LEARNING_RATE = 0.0125` +> * `Current Learning Rate = 0.125` +> * `Previous Base Fee = 10.0` + +### Block is Completely Empty + +In this example, we expect the learning rate to additively increase and the base +fee to decrease. + +```golang +blockConsumption := sumBlockGasInWindow(1) / (1 * 100) == 0 +newLearningRate := min(1.0, 0.025 + 0.125) == 0.15 +newBaseGasPrice := 10 * (1 + 0.15 * (0 - 50) / 50) == 8.5 +``` + +As we can see, the base fee decreased by 1.5 and the learning rate increases. + +### Block is Completely Full + +In this example, we expect the learning rate to additively increase and the base +fee to increase. + +```golang +blockConsumption := sumBlockGasInWindow(1) / (1 * 100) == 1 +newLearningRate := min(1.0, 0.025 + 0.125) == 0.15 +newBaseGasPrice := 10 * (1 + 0.15 * ((100 - 50) / 50)) == 11.5 +``` + +As we can see, the base fee increased by 1.5 and the learning rate increases. + +### Block is at Target Gas + +In this example, we expect the learning rate to multiplicatively decrease and the +base fee to remain the same. + +```golang +blockConsumption := sumBlockGasInWindow(1) / (1 * 100) == 0.5 +newLearningRate := max(0.0125, 0.95 * 0.125) == 0.11875 +newBaseGasPrice := 10 * (1 + 0.11875 * (50 - 50) / 50) == 10 +``` + +As we can see, the base fee remained the same and the learning rate decreased. + +## Default EIP-1559 With AIMD EIP-1559 + +It is entirely possible to implement the default EIP-1559 dynamic fee pricing +with the AIMD EIP-1559 dynamic fee pricing. This can be done by setting the +following parameters: + +* `Alpha = 0.0` +* `Beta = 1.0` +* `Gamma = 1.0` +* `MAX_LEARNING_RATE = 0.125` +* `MIN_LEARNING_RATE = 0.125` +* `Window = 1` diff --git a/x/dynamicfee/CLIENTS.md b/x/dynamicfee/CLIENTS.md new file mode 100644 index 000000000000..805f86a0d037 --- /dev/null +++ b/x/dynamicfee/CLIENTS.md @@ -0,0 +1,91 @@ +# Gas Price Queries for Integrations + +Because `x/dynamicfee` uses a dynamic fee, end-users will need to query the module +for the current `gasPrice` to use when building a transaction. + +A summary for the flow to query this information is as follows: + +* Create an RPC connection with the chain +* Create a `dynamicfee` client +* Submit the `GasPrice` query +* Use the `gasPrice` to populate the Tx fee field. + +Extensive querying information can be seen in the module [spec](README.md#query). + +The specific query for `GasPrices` can be found [here](README.md#gas-prices). + +## Code Snippet + +Wallet, relayers, and other users will want to add programmatic ways to query +this before building their transactions. Below is an example of how a user could +implement this lightweight query in Go: + +### Create A gRPC Connection + +First, a base connection to the chain you are querying must be created. + +A chain gRPC (below) or CometBFT ABCI RPC connection can be created: + +```go + // Set up gRPC connection to chain + cc, err := grpc.NewClient(endpoint, insecure.NewCredentials()) + if err != nil { + panic(err) + } + defer cc.Close() +``` + +### Create a Dynamicfee Query Client + +An `x/dynamicfee` query client can then be created using the created gRPC connection. + +This client exposes all [queries](README.md#query) that the `x/dynamicfee` module +exposes. + +```go + // Create DynamicfeeClient with underlying gRPC connection + dynamicfeeClient := dynamicfeetypes.NewQueryClient(cc) +``` + +### Query Gas Prices + +The `gas price` (as an `sdk.DecCoin`) can be queried using the `GasPrice` query. +This query requires the desired coin denomination for the fee to be paid with. + +The query will return an error if the given denomination is not supported. + +```go + gasPrice, err := dynamicfeeClient.GasPrice(ctx, &dynamicfeetypes.GasPriceRequest{ + Denom: denom, + }) + if err != nil { + panic(err) + } +``` + +### Using `gasPrice` to construct a transaction + +There are two ways to construct a transaction with `gasPrice`: + +1. Provide the minimum fee: `feeAmount = gasPrice * gasLimit` (`gasLimit` gives + the maximum amount of gas a transaction can consume. You can obtain + appropriate `gasLimit` by simulating a transaction to see how much gas + it consumes under normal conditions). +2. Provide a higher fee with respect to the minimum fee: + `feeAmount=gasPrice * gasLimit + tip`; this will result in your transaction + being placed ahead of others with lower tips (or being included in the block + instead of others when the block is full) + +## Examples of Other EIP-1559 Integrations + +The original [`skip-mev/feemarket`](https://github.com/skip-mev/feemarket) from +which this module is based on provides a similar implementation. + +Also, the [Osmosis](https://github.com/osmosis-labs/osmosis) Blockchain has a similar +EIP-1559 mechanism that has been integrated by wallets and relayers. Below are +some examples as to how different projects query the dynamic fee for transactions: + +* [Keplr Wallet EIP-1559 BaseFee Query](https://github.com/chainapsis/keplr-wallet/blob/b0a96c2c713d8163ce840fcd5abbac4eb612607c/packages/stores/src/query/osmosis/base-fee/index.ts#L18) +* [Cosmos-Relayer EIP-1559 BaseFee Query](https://github.com/cosmos/relayer/blob/9b140b664fe6b10161af1093ccd26627b942742e/relayer/chains/cosmos/fee_market.go#L13) +* [Hermes Relayer EIP-1559 Fee Query](https://github.com/informalsystems/hermes/blob/fc8376ba98e4b595e446b366b736a0c046d6026a/crates/relayer/src/chain/cosmos/eip_base_fee.rs#L15) + * Note: Hermes also already implements a query `x/feemarket` seen [here](https://github.com/informalsystems/hermes/blob/fc8376ba98e4b595e446b366b736a0c046d6026a/crates/relayer/src/chain/cosmos/eip_base_fee.rs#L33) diff --git a/x/dynamicfee/README.md b/x/dynamicfee/README.md new file mode 100644 index 000000000000..1958bfccd680 --- /dev/null +++ b/x/dynamicfee/README.md @@ -0,0 +1,590 @@ +--- +sidebar_position: 1 +--- + +# `x/dynamicfee` + +## Abstract + +This document describes the specifications of the AtomOne implementation +of the `x/dynamicfee` module. This module is a fork of the +[`skip-mev/feemarket`](https://github.com/skip-mev/feemarket) module +(more specifically, of the [`sdk-47`](https://github.com/skip-mev/feemarket/tree/sdk-47) +branch) and includes changes and adaptations to suit the AtomOne project. + +## Contents + +- [`x/dynamicfee`](#xdynamicfee) + - [Abstract](#abstract) + - [Contents](#contents) + - [Concepts](#concepts) + - [Additive Increase Multiplicative Decrease (AIMD) EIP-1559](#additive-increase-multiplicative-decrease-aimd-eip-1559) + - [Fee deduction and naive Tx prioritization](#fee-deduction-and-naive-tx-prioritization) + - [Module state updates](#module-state-updates) + - [State](#state) + - [GasPrice](#gasprice) + - [LearningRate](#learningrate) + - [Window](#window) + - [Index](#index) + - [Keeper](#keeper) + - [Messages](#messages) + - [MsgParams](#msgparams) + - [Events](#events) + - [Tx](#tx) + - [Parameters](#parameters) + - [Alpha](#alpha) + - [Beta](#beta) + - [Gamma](#gamma) + - [MinBaseGasPrice](#minbasegasprice) + - [TargetBlockUtilization](#targetblockutilization) + - [MinLearningRate](#minlearningrate) + - [MaxLearningRate](#maxlearningrate) + - [Window](#window-1) + - [FeeDenom](#feedenom) + - [Enabled](#enabled) + - [Client](#client) + - [CLI](#cli) + - [Query](#query) + - [params](#params) + - [state](#state-1) + - [gas-price](#gas-price) + - [gas-prices](#gas-prices) + - [gRPC](#grpc) + - [Params](#params-1) + - [State](#state-2) + - [GasPrice](#gasprice-1) + - [GasPrices](#gasprices) + +## Concepts + +### Additive Increase Multiplicative Decrease (AIMD) EIP-1559 + +Please refer to [AIMD.md](AIMD.md) for a detailed description of the AIMD EIP-1559 + +### Fee deduction and naive Tx prioritization + +Fee deduction is performed in the `anteHandler`. The entire user-set fee is +deducted from the user's account and sent to the `x/distribution` module account. +In order for a transaction to be included in a block, the transaction's gas price +must be at least equat to the current gas price. However, users can also specify +an even higher gas price than the current gas price to increase the priority of +their transaction. A naive form of transactions prioritization is implemented so +that transactions with higher gas prices are included in the block with higher priority. + +### Module state updates + +The `dynamicfee` module updates the gas consumed in the current block on a per-tx +basis relying on the `postHandler`. Updates to the base fee and learning rate +are instead performed in the `endBlocker`. + +## State + +The `x/dynamicfee` module keeps state of the following primary objects: + +1. Current base-fee +2. Current learning rate +3. Moving window of block gas + +In addition, the `x/dynamicfee` module keeps the following indexes to manage the +aforementioned state: + +* State: `0x02 |ProtocolBuffer(State)` + +### GasPrice + +GasPrice is the current gas price. This is denominated in the fee per gas +unit in the base fee denom. + +### LearningRate + +LearningRate is the current learning rate. + +### Window + +Window contains a list of the last blocks' gas values. This is used +to calculate the next base fee. This stores the number of units of gas +consumed per block. + +### Index + +Index is the index of the current block in the block gas window. + +```protobuf +// State is utilized to track the current state of the dynamic fee pricer. This +// includes the current base fee, learning rate, and block gas within the +// specified AIMD window. +message State { + // BaseGasPrice is the current base fee. This is denominated in the fee per gas + // unit. + string base_gas_price = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // LearningRate is the current learning rate. + string learning_rate = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Window contains a list of the last blocks' gas values. This is used + // to calculate the next base fee. This stores the number of units of gas + // consumed per block. + repeated uint64 window = 3; + + // Index is the index of the current block in the block gas window. + uint64 index = 4; +} +``` + +## Keeper + +The dynamicfee module provides a keeper interface for accessing the KVStore. + +```go +type DynamicfeeKeeper interface { + // Get the current state from the store. + GetState(ctx sdk.Context) (types.State, error) + + // Set the state in the store. + SetState(ctx sdk.Context, state types.State) error + + // Get the current params from the store. + GetParams(ctx sdk.Context) (types.Params, error) + + // Set the params in the store. + SetParams(ctx sdk.Context, params types.Params) error + + // Get the minimum gas price for a given denom from the store. + GetMinGasPrice(ctx sdk.Context, denom string) (sdk.DecCoin, error) { + + // Get the current minimum gas prices from the store. + GetMinGasPrices(ctx sdk.Context) (sdk.DecCoins, error) +} +``` + +## Messages + +### MsgParams + +The `dynamicfee` module params can be updated through `MsgParams`, which can be +done using a governance proposal. The signer will always be the `gov` module +account address. + +```protobuf +message MsgParams { + option (cosmos.msg.v1.signer) = "authority"; + + // Params defines the new parameters for the dynamicfee module. + Params params = 1 [ (gogoproto.nullable) = false ]; + // Authority defines the authority that is updating the dynamicfee module + // parameters. + string authority = 2 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; +} +``` + +The message handling can fail if: + +* signer is not the gov module account address. + +## Events + +The dynamicfee module emits the following events: + +### Tx + +```json +{ + "type": "tx", + "attributes": [ + { + "key": "fee", + "value": "{{sdk.Coins being payed}}", + "index": true + }, + { + "key": "fee_payer", + "value": "{{sdk.AccAddress paying the fees}}", + "index": true + } + ] +} +``` + +## Parameters + +The dynamicfee module stores its params in state with the prefix of `0x01`, +which can be updated with governance or the address with authority. + +* Params: `0x01 | ProtocolBuffer(Params)` + +The dynamicfee module contains the following parameters: + +### Alpha + +Alpha is the amount we added to the learning rate +when it is above or below the target +/- threshold. + +### Beta + +Beta is the amount we multiplicatively decrease the learning rate +when it is within the target +/- threshold. + +### Gamma + +Gamma is the threshold for the learning rate. If the learning rate is +above or below the target +/- threshold, we additively increase the +learning rate by Alpha. Otherwise, we multiplicatively decrease the +learning rate by Beta. + +### MinBaseGasPrice + +MinBaseGasPrice determines the initial gas price of the module and the global +minimum for the network. This is denominated in fee per gas unit in the `FeeDenom`. + +### TargetBlockUtilization + +TargetBlockUtilization is the target block utilization expressed as a percentage +of the block gas limit. + +### MinLearningRate + +MinLearningRate is the lower bound for the learning rate. + +### MaxLearningRate + +MaxLearningRate is the upper bound for the learning rate. + +### Window + +Window defines the window size for calculating an adaptive learning rate +over a moving window of blocks. The default EIP1559 implementation uses +a window of size 1. + +### FeeDenom + +FeeDenom is the denom that will be used for all fee payments. + +### Enabled + +Enabled is a boolean that determines whether the EIP1559 dynamic fee pricing +is enabled. This can be used to add the dynamicfee module and enable it +through governance at a later time. + +```protobuf +// Params contains the required set of parameters for the EIP1559 dynamic fee +// pricing implementation. +message Params { + // Alpha is the amount we additively increase the learning rate + // when it is above or below the target +/- threshold. + // + // Must be > 0. + string alpha = 1 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Beta is the amount we multiplicatively decrease the learning rate + // when it is within the target +/- threshold. + // + // Must be [0, 1]. + string beta = 2 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Gamma is the threshold for the learning rate. If the learning rate is + // above or below the target +/- threshold, we additively increase the + // learning rate by Alpha. Otherwise, we multiplicatively decrease the + // learning rate by Beta. + // + // Must be [0, 0.5]. + string gamma = 3 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // MinBaseGasPrice determines the initial gas price of the module and the + // global minimum for the network. + string min_base_gas_price = 5 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // TargetBlockUtilization is the target block utilization expressed as a + // decimal value between 0 and 1. It is the target percentage utilization + // of the block in relation to the consensus_params.block.max_gas parameter. + string target_block_utilization = 6 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // MinLearningRate is the lower bound for the learning rate. + string min_learning_rate = 7 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // MaxLearningRate is the upper bound for the learning rate. + string max_learning_rate = 8 [ + (cosmos_proto.scalar) = "cosmos.Dec", + (gogoproto.customtype) = "cosmossdk.io/math.LegacyDec", + (gogoproto.nullable) = false + ]; + + // Window defines the window size for calculating an adaptive learning rate + // over a moving window of blocks. + uint64 window = 9; + + // FeeDenom is the denom that will be used for all fee payments. + string fee_denom = 10; + + // Enabled is a boolean that determines whether the EIP1559 dynamic fee + // pricing is enabled. + bool enabled = 11; +} +``` + +## Client + +### CLI + +A user can query and interact with the `dynamicfee` module using the CLI. + +#### Query + +The `query` commands allow users to query `dynamicfee` state. + +```shell +atomoned query dynamicfee --help +``` + +##### params + +The `params` command allows users to query the on-chain parameters. + +```shell +atomoned query dynamicfee params [flags] +``` + +Example: + +```shell +atomoned query dynamicfee params +``` + +Example Output: + +```yml +alpha: "0.000000000000000000" +beta: "1.000000000000000000" +enabled: true +fee_denom: uatone +gamma: "0.000000000000000000" +max_learning_rate: "0.125000000000000000" +min_base_gas_price: "1.000000000000000000" +min_learning_rate: "0.125000000000000000" +target_block_utilization: "0.500000000000000000" +window: "1" +``` + +##### state + +The `state` command allows users to query the current on-chain state. + +```shell +atomoned query dynamicfee state [flags] +``` + +Example: + +```shell +atomoned query dynamicfee state +``` + +Example Output: + +```yml +base_fee: "1.000000000000000000" +index: "0" +learning_rate: "0.125000000000000000" +window: + - "0" +``` + +##### gas-price + +The `gas-price` command allows users to query the current gas-price for a given denom. + +```shell +atomoned query dynamicfee gas-price [denom] [flags] +``` + +Example: + +```shell +atomoned query dynamicfee gas-price uatone +``` + +Example Output: + +```yml +1000000uatone +``` + +##### gas-prices + +The `gas-prices` command allows users to query the current gas-price for all +supported denoms. + +```shell +atomoned query dynamicfee gas-prices [flags] +``` + +Example: + +```shell +atomoned query dynamicfee gas-prices +``` + +Example Output: + +```yml +1000000stake,100000uatone +``` + +## gRPC + +A user can query the `dynamicfee` module using gRPC endpoints. + +### Params + +The `Params` endpoint allows users to query the on-chain parameters. + +```shell +atomone.dynamicfee.v1.Query/Params +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + atomone.dynamicfee.v1.Query/Params +``` + +Example Output: + +```json +{ + "params": { + "alpha": "0", + "beta": "1000000000000000000", + "gamma": "0", + "minBaseGasPrice": "1000000", + "minLearningRate": "125000000000000000", + "maxLearningRate": "125000000000000000", + "targetBlockUtilization": "500000000000000000", + "window": "1", + "feeDenom": "uatone", + "enabled": true + } +} +``` + +### State + +The `State` endpoint allows users to query the current on-chain state. + +```shell +atomone.dynamicfee.v1.Query/State +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + atomone.dynamicfee.v1.Query/State +``` + +Example Output: + +```json +{ + "state": { + "baseGasPrice": "1000000", + "learningRate": "125000000000000000", + "window": [ + "0" + ] + } +} +``` + +### GasPrice + +The `GasPrice` endpoint allows users to query the current on-chain gas price for +a given denom. + +```shell +atomone.dynamicfee.v1.Query/GasPrice +``` + +Example: + +```shell +grpcurl -plaintext \ + -d '{"denom": "uatone"}' \ + localhost:9090 \ + atomone.dynamicfee.v1.Query/GasPrice/ +``` + +Example Output: + +```json +{ + "price": { + "denom": "uatone", + "amount": "1000000" + } +} +``` + +### GasPrices + +The `GasPrices` endpoint allows users to query the current on-chain gas prices +for all denoms. + +```shell +atomone.dynamicfee.v1.Query/GasPrices +``` + +Example: + +```shell +grpcurl -plaintext \ + localhost:9090 \ + atomone.dynamicfee.v1.Query/GasPrices +``` + +Example Output: + +```json +{ + "prices": [ + { + "denom": "uatone", + "amount": "1000000" + } + ] +} +``` diff --git a/x/dynamicfee/ante/expected_keepers.go b/x/dynamicfee/ante/expected_keepers.go new file mode 100644 index 000000000000..af706f88dbc4 --- /dev/null +++ b/x/dynamicfee/ante/expected_keepers.go @@ -0,0 +1,29 @@ +package ante + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +type DynamicfeeKeeper interface { + GetMinGasPrice(context.Context, string) (sdk.DecCoin, error) + GetParams(context.Context) (types.Params, error) +} + +// AccountKeeper defines the contract needed for AccountKeeper related APIs. +// Interface provides support to use non-sdk AccountKeeper for AnteHandler's decorators. +type AccountKeeper interface { + GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI +} + +// FeeGrantKeeper defines the expected feegrant keeper. +type FeeGrantKeeper interface { + UseGrantedFees(ctx context.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error +} + +// BankKeeper defines the contract needed for supply related APIs. +type BankKeeper interface { + SendCoinsFromAccountToModule(ctx context.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error +} diff --git a/x/dynamicfee/ante/expected_keepers_mocks_test.go b/x/dynamicfee/ante/expected_keepers_mocks_test.go new file mode 100644 index 000000000000..6eb2bc65f828 --- /dev/null +++ b/x/dynamicfee/ante/expected_keepers_mocks_test.go @@ -0,0 +1,178 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/dynamicfee/ante/expected_keepers.go + +// Package ante_test is a generated GoMock package. +package ante_test + +import ( + context "context" + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/types" + types0 "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" + gomock "github.com/golang/mock/gomock" +) + +// MockDynamicfeeKeeper is a mock of DynamicfeeKeeper interface. +type MockDynamicfeeKeeper struct { + ctrl *gomock.Controller + recorder *MockDynamicfeeKeeperMockRecorder +} + +// MockDynamicfeeKeeperMockRecorder is the mock recorder for MockDynamicfeeKeeper. +type MockDynamicfeeKeeperMockRecorder struct { + mock *MockDynamicfeeKeeper +} + +// NewMockDynamicfeeKeeper creates a new mock instance. +func NewMockDynamicfeeKeeper(ctrl *gomock.Controller) *MockDynamicfeeKeeper { + mock := &MockDynamicfeeKeeper{ctrl: ctrl} + mock.recorder = &MockDynamicfeeKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDynamicfeeKeeper) EXPECT() *MockDynamicfeeKeeperMockRecorder { + return m.recorder +} + +// GetMinGasPrice mocks base method. +func (m *MockDynamicfeeKeeper) GetMinGasPrice(arg0 context.Context, arg1 string) (types.DecCoin, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMinGasPrice", arg0, arg1) + ret0, _ := ret[0].(types.DecCoin) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMinGasPrice indicates an expected call of GetMinGasPrice. +func (mr *MockDynamicfeeKeeperMockRecorder) GetMinGasPrice(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMinGasPrice", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).GetMinGasPrice), arg0, arg1) +} + +// GetParams mocks base method. +func (m *MockDynamicfeeKeeper) GetParams(arg0 context.Context) (types0.Params, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", arg0) + ret0, _ := ret[0].(types0.Params) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockDynamicfeeKeeperMockRecorder) GetParams(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).GetParams), arg0) +} + +// MockAccountKeeper is a mock of AccountKeeper interface. +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +// NewMockAccountKeeper creates a new mock instance. +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +// GetAccount mocks base method. +func (m *MockAccountKeeper) GetAccount(ctx context.Context, addr types.AccAddress) types.AccountI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAccount", ctx, addr) + ret0, _ := ret[0].(types.AccountI) + return ret0 +} + +// GetAccount indicates an expected call of GetAccount. +func (mr *MockAccountKeeperMockRecorder) GetAccount(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccount", reflect.TypeOf((*MockAccountKeeper)(nil).GetAccount), ctx, addr) +} + +// MockFeeGrantKeeper is a mock of FeeGrantKeeper interface. +type MockFeeGrantKeeper struct { + ctrl *gomock.Controller + recorder *MockFeeGrantKeeperMockRecorder +} + +// MockFeeGrantKeeperMockRecorder is the mock recorder for MockFeeGrantKeeper. +type MockFeeGrantKeeperMockRecorder struct { + mock *MockFeeGrantKeeper +} + +// NewMockFeeGrantKeeper creates a new mock instance. +func NewMockFeeGrantKeeper(ctrl *gomock.Controller) *MockFeeGrantKeeper { + mock := &MockFeeGrantKeeper{ctrl: ctrl} + mock.recorder = &MockFeeGrantKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFeeGrantKeeper) EXPECT() *MockFeeGrantKeeperMockRecorder { + return m.recorder +} + +// UseGrantedFees mocks base method. +func (m *MockFeeGrantKeeper) UseGrantedFees(ctx context.Context, granter, grantee types.AccAddress, fee types.Coins, msgs []types.Msg) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UseGrantedFees", ctx, granter, grantee, fee, msgs) + ret0, _ := ret[0].(error) + return ret0 +} + +// UseGrantedFees indicates an expected call of UseGrantedFees. +func (mr *MockFeeGrantKeeperMockRecorder) UseGrantedFees(ctx, granter, grantee, fee, msgs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseGrantedFees", reflect.TypeOf((*MockFeeGrantKeeper)(nil).UseGrantedFees), ctx, granter, grantee, fee, msgs) +} + +// MockBankKeeper is a mock of BankKeeper interface. +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +// NewMockBankKeeper creates a new mock instance. +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +// SendCoinsFromAccountToModule mocks base method. +func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx context.Context, senderAddr types.AccAddress, recipientModule string, amt types.Coins) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendCoinsFromAccountToModule indicates an expected call of SendCoinsFromAccountToModule. +func (mr *MockBankKeeperMockRecorder) SendCoinsFromAccountToModule(ctx, senderAddr, recipientModule, amt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromAccountToModule", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromAccountToModule), ctx, senderAddr, recipientModule, amt) +} diff --git a/x/dynamicfee/ante/fee.go b/x/dynamicfee/ante/fee.go new file mode 100644 index 000000000000..3ddc146941a9 --- /dev/null +++ b/x/dynamicfee/ante/fee.go @@ -0,0 +1,289 @@ +package ante + +import ( + "bytes" + "math" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// BankSendGasConsumption is the gas consumption of the bank send that occur during dynamicfee handler execution. +const BankSendGasConsumption = 385 + +type dynamicfeeCheckDecorator struct { + dynamicfeeKeeper DynamicfeeKeeper + bankKeeper BankKeeper + feegrantKeeper FeeGrantKeeper + accountKeeper AccountKeeper +} + +func newDynamicfeeCheckDecorator(ak AccountKeeper, bk BankKeeper, fk FeeGrantKeeper, fmk DynamicfeeKeeper) dynamicfeeCheckDecorator { + return dynamicfeeCheckDecorator{ + dynamicfeeKeeper: fmk, + bankKeeper: bk, + feegrantKeeper: fk, + accountKeeper: ak, + } +} + +// DynamicfeeCheckDecorator checks sufficient fees from the fee payer based off of the current +// state of the dynamicfee. +// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error. +// Call next AnteHandler if fees successfully checked. +// +// If x/dynamicfee is disabled (params.Enabled == false), the handler will fall back to the default +// Cosmos SDK fee deduction antehandler. +// +// CONTRACT: Tx must implement FeeTx interface +type DynamicfeeCheckDecorator struct { + dynamicfeeKeeper DynamicfeeKeeper + + dynamicfeeDecorator dynamicfeeCheckDecorator + fallbackDecorator sdk.AnteDecorator +} + +func NewDynamicfeeCheckDecorator(ak AccountKeeper, bk BankKeeper, fk FeeGrantKeeper, fmk DynamicfeeKeeper, fallbackDecorator sdk.AnteDecorator) DynamicfeeCheckDecorator { + return DynamicfeeCheckDecorator{ + dynamicfeeKeeper: fmk, + dynamicfeeDecorator: newDynamicfeeCheckDecorator( + ak, bk, fk, fmk, + ), + fallbackDecorator: fallbackDecorator, + } +} + +// AnteHandle calls the dynamicfee internal antehandler if the keeper is enabled. If disabled, the fallback +// fee antehandler is fallen back to. +func (d DynamicfeeCheckDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + params, err := d.dynamicfeeKeeper.GetParams(ctx) + if err != nil { + return ctx, err + } + if params.Enabled { + return d.dynamicfeeDecorator.anteHandle(ctx, tx, simulate, next) + } + + // only use fallback if not nil + if d.fallbackDecorator != nil { + return d.fallbackDecorator.AnteHandle(ctx, tx, simulate, next) + } + + return next(ctx, tx, simulate) +} + +// anteHandle checks if the tx provides sufficient fee to cover the required fee from the dynamic fee pricing. +func (dfd dynamicfeeCheckDecorator) anteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { + // GenTx consume no fee + if ctx.BlockHeight() == 0 { + return next(ctx, tx, simulate) + } + + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 { + return ctx, sdkerrors.ErrInvalidGasLimit.Wrapf("must provide positive gas") + } + + params, err := dfd.dynamicfeeKeeper.GetParams(ctx) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to get dynamicfee params") + } + + // return if disabled + if !params.Enabled { + return next(ctx, tx, simulate) + } + + feeCoins := feeTx.GetFee() + gas := feeTx.GetGas() // use provided gas limit + + if len(feeCoins) == 0 && !simulate { + return ctx, errorsmod.Wrapf(types.ErrNoFeeCoins, "got length %d", len(feeCoins)) + } + if len(feeCoins) > 1 { + return ctx, errorsmod.Wrapf(types.ErrTooManyFeeCoins, "got length %d", len(feeCoins)) + } + + // if simulating - create a dummy zero value for the user + payCoin := sdk.NewCoin(params.FeeDenom, sdkmath.ZeroInt()) + if !simulate { + payCoin = feeCoins[0] + } + + feeGas := int64(feeTx.GetGas()) + + minGasPrice, err := dfd.dynamicfeeKeeper.GetMinGasPrice(ctx, payCoin.GetDenom()) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to get min gas price for denom %s", payCoin.GetDenom()) + } + + ctx.Logger().Info("fee deduct ante handle", + "min gas prices", minGasPrice, + "fee", feeCoins, + "gas limit", gas, + ) + + ctx = ctx.WithMinGasPrices(sdk.NewDecCoins(minGasPrice)) + + if !simulate { + err := CheckTxFee(ctx, minGasPrice, payCoin, feeGas) + if err != nil { + return ctx, errorsmod.Wrapf(err, "error checking fee") + } + } + + // deduct the entire amount that the account provided as fee (payCoin) + err = dfd.DeductFees(ctx, tx, payCoin) + if err != nil { + return ctx, errorsmod.Wrapf(err, "error deducting fee") + } + if simulate { + ctx.GasMeter().ConsumeGas(BankSendGasConsumption, "simulation send gas consumption") + } + + // Compute tx priority + if payCoin.Denom == params.FeeDenom { + // Same denom no conversion needed + ctx = ctx.WithPriority(GetTxPriority(payCoin, int64(gas), minGasPrice)) + } else { + // Different denom, payCoin needs to be converted to params.FeeDenom + // 1. get gas price in params.FeeDenom + baseGasPrice, err := dfd.dynamicfeeKeeper.GetMinGasPrice(ctx, params.FeeDenom) + if err != nil { + return ctx, err + } + // 2. compute conversion factor between the 2 denoms + factor := baseGasPrice.Amount.Quo(minGasPrice.Amount) + // 3. convert payCoin + feeCoin := sdk.NewCoin( + params.FeeDenom, + payCoin.Amount.ToLegacyDec().Mul(factor).TruncateInt(), + ) + // 4. compute tx priority + ctx = ctx.WithPriority(GetTxPriority(feeCoin, int64(gas), baseGasPrice)) + } + + return next(ctx, tx, simulate) +} + +// DeductFees deducts the provided fee from the payer account during tx execution. +func (dfd dynamicfeeCheckDecorator) DeductFees(ctx sdk.Context, sdkTx sdk.Tx, providedFee sdk.Coin) error { + feeTx, ok := sdkTx.(sdk.FeeTx) + if !ok { + return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") + } + + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() + deductFeesFrom := feePayer + + // if feegranter set deduct fee from feegranter account. + // this works with only when feegrant enabled. + if feeGranter != nil { + if dfd.feegrantKeeper == nil { + return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") + } else if !bytes.Equal(feeGranter, feePayer) { + if !providedFee.IsNil() { + err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, sdk.NewCoins(providedFee), sdkTx.GetMsgs()) + if err != nil { + return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", sdk.AccAddress(feeGranter).String(), sdk.AccAddress(feePayer).String()) + } + } + } + + deductFeesFrom = feeGranter + } + + deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) + if deductFeesFromAcc == nil { + return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", sdk.AccAddress(deductFeesFrom).String()) + } + + err := dfd.bankKeeper.SendCoinsFromAccountToModule(ctx, deductFeesFromAcc.GetAddress(), authtypes.FeeCollectorName, sdk.NewCoins(providedFee)) + if err != nil { + return errorsmod.Wrap(sdkerrors.ErrInsufficientFunds, err.Error()) + } + + events := sdk.Events{ + sdk.NewEvent( + sdk.EventTypeTx, + sdk.NewAttribute(sdk.AttributeKeyFee, providedFee.String()), + sdk.NewAttribute(sdk.AttributeKeyFeePayer, sdk.AccAddress(deductFeesFrom).String()), + ), + } + ctx.EventManager().EmitEvents(events) + + return nil +} + +// CheckTxFee implements the logic for the dynamic fee pricing to check if a Tx +// has provided sufficient fees given the current state of the dynamic fee +// pricer. Returns an error if insufficient fees. +func CheckTxFee(_ sdk.Context, gasPrice sdk.DecCoin, feeCoin sdk.Coin, feeGas int64) error { + // Ensure that the provided fees meet the minimum + if !gasPrice.IsZero() { + var requiredFee sdk.Coin + + glDec := sdkmath.LegacyNewDec(feeGas) + limitFee := gasPrice.Amount.Mul(glDec) + requiredFee = sdk.NewCoin(gasPrice.Denom, limitFee.Ceil().RoundInt()) + + if !feeCoin.IsGTE(requiredFee) { + return sdkerrors.ErrInsufficientFee.Wrapf( + "got: %s required: %s, minGasPrice: %s", + feeCoin, + requiredFee, + gasPrice, + ) + } + } + + return nil +} + +const ( + // gasPricePrecision is the amount of digit precision to scale the gas prices to. + gasPricePrecision = 6 +) + +// GetTxPriority returns a naive tx priority based on the amount of gas price provided in a transaction. +// +// The fee amount is divided by the gasLimit to calculate "Effective Gas Price". +// This value is then normalized and scaled into an integer, so it can be used as a priority. +// +// effectiveGasPrice = feeAmount / gas limit (denominated in fee per gas) +// normalizedGasPrice = effectiveGasPrice / currentGasPrice (floor is 1. The minimum effective gas price can ever be is current gas price) +// scaledGasPrice = normalizedGasPrice * 10 ^ gasPricePrecision (amount of decimal places in the normalized gas price to consider when converting to int64). +func GetTxPriority(fee sdk.Coin, gasLimit int64, currentGasPrice sdk.DecCoin) int64 { + // protections from dividing by 0 + if gasLimit == 0 { + return 0 + } + + // if the gas price is 0, just use a raw amount + if currentGasPrice.IsZero() { + return fee.Amount.Int64() + } + + effectiveGasPrice := fee.Amount.ToLegacyDec().QuoInt64(gasLimit) + normalizedGasPrice := effectiveGasPrice.Quo(currentGasPrice.Amount) + scaledGasPrice := normalizedGasPrice.MulInt64(int64(math.Pow10(gasPricePrecision))) + + // overflow panic protection + if scaledGasPrice.GTE(sdkmath.LegacyNewDec(math.MaxInt64)) { + return math.MaxInt64 + } else if scaledGasPrice.LTE(sdkmath.LegacyOneDec()) { + return 0 + } + + return scaledGasPrice.TruncateInt64() +} diff --git a/x/dynamicfee/ante/fee_test.go b/x/dynamicfee/ante/fee_test.go new file mode 100644 index 000000000000..c64d27ca3863 --- /dev/null +++ b/x/dynamicfee/ante/fee_test.go @@ -0,0 +1,489 @@ +package ante_test + +import ( + "errors" + "fmt" + "testing" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/gogoproto/proto" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + xtxsigning "cosmossdk.io/x/tx/signing" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/address" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/ante" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +type mocks struct { + ctx sdk.Context + AccountKeeper *MockAccountKeeper + BankKeeper *MockBankKeeper + FeeGrantKeeper *MockFeeGrantKeeper + DynamicfeeKeeper *MockDynamicfeeKeeper +} + +func setupMocks(t *testing.T) mocks { + t.Helper() + ctrl := gomock.NewController(t) + return mocks{ + ctx: sdk.NewContext(nil, tmproto.Header{}, false, log.NewTestLogger(t)), + AccountKeeper: NewMockAccountKeeper(ctrl), + BankKeeper: NewMockBankKeeper(ctrl), + FeeGrantKeeper: NewMockFeeGrantKeeper(ctrl), + DynamicfeeKeeper: NewMockDynamicfeeKeeper(ctrl), + } +} + +func TestAnteHandle(t *testing.T) { + interfaceRegistry, _ := codectypes.NewInterfaceRegistryWithOptions(codectypes.InterfaceRegistryOptions{ + ProtoFiles: proto.HybridResolver, + SigningOptions: xtxsigning.Options{ + AddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(), + }, + ValidatorAddressCodec: address.Bech32Codec{ + Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(), + }, + }, + }) + + txConfig := authtx.NewTxConfig( + codec.NewProtoCodec(interfaceRegistry), + []signing.SignMode{signing.SignMode_SIGN_MODE_DIRECT}, + ) + + var ( + addrs = simtestutil.CreateIncrementalAccounts(3) + acc1 = authtypes.NewBaseAccountWithAddress(addrs[0]) + acc2 = authtypes.NewBaseAccountWithAddress(addrs[1]) + acc3 = authtypes.NewBaseAccountWithAddress(addrs[2]) + ) + + tests := []struct { + name string + tx func() sdk.Tx + genTx bool + simulate bool + disableFeeGrant bool + setup func(mocks) + expectedConsumedGas int + expectedMinGasPrices string + expectedTxPriority int64 + expectedError string + }{ + { + name: "ok: gentx requires no gas", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + return txBuilder.GetTx() + }, + genTx: true, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil) + }, + expectedMinGasPrices: "", + expectedTxPriority: 0, + }, + { + name: "fail: 0 gas given", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil) + }, + expectedError: "must provide positive gas: invalid gas limit", + }, + { + name: "ok: 0 gas given with disabled dynamicfee", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + params := types.DefaultParams() + params.Enabled = false + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx).Return(params, nil) + }, + }, + { + name: "ok: simulate --gas=auto behavior", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + return txBuilder.GetTx() + }, + simulate: true, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[0]). + Return(acc1) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[0], authtypes.FeeCollectorName, sdk.NewCoins()) + }, + expectedConsumedGas: ante.BankSendGasConsumption, + expectedMinGasPrices: "1.000000000000000000stake", + expectedTxPriority: 0, + }, + { + name: "fail: 0 fee given", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + }, + expectedError: "got length 0: no fee coin provided. Must provide one.", + }, + { + name: "fail: too many fee coins given", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 1), + sdk.NewInt64Coin("photon", 2), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + }, + expectedError: "got length 2: too many fee coins provided. Only one fee coin may be provided", + }, + { + name: "fail: getMinGasPrice returns an error", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 2), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.DecCoin{}, errors.New("OUPS")) + }, + expectedError: "unable to get min gas price for denom stake: OUPS", + }, + { + name: "fail: not enough fee", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 1), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + }, + expectedError: "error checking fee: got: 1stake required: 42stake, minGasPrice: 1.000000000000000000stake: insufficient fee", + }, + { + name: "fail: unknown account", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[0]).Return(nil) + }, + expectedError: fmt.Sprintf( + "error deducting fee: fee payer address: %s does not exist: unknown address", + addrs[0], + ), + }, + { + name: "ok: enough fee", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[0]). + Return(acc1) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[0], authtypes.FeeCollectorName, + sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 42))) + }, + expectedMinGasPrices: "1.000000000000000000stake", + expectedTxPriority: 1000000, + }, + { + name: "ok: more fee than gas limit increases tx priority", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 420), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[0]). + Return(acc1) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[0], authtypes.FeeCollectorName, + sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 420))) + }, + expectedMinGasPrices: "1.000000000000000000stake", + expectedTxPriority: 10000000, + }, + { + name: "ok: enough fee with different denom", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin("uatone", 420), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, "uatone"). + Return(sdk.NewInt64DecCoin("uatone", 10), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[0]). + Return(acc1) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[0], authtypes.FeeCollectorName, + sdk.NewCoins(sdk.NewInt64Coin("uatone", 420))) + // second call to GetMinGasPrice for tx priority computation + ctx := m.ctx.WithMinGasPrices(sdk.NewDecCoins( + sdk.NewInt64DecCoin("uatone", 10), + )) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + }, + expectedMinGasPrices: "10.000000000000000000uatone", + expectedTxPriority: 1000000, + }, + { + name: "ok: enough fee with named payer", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + txBuilder.SetFeePayer(addrs[1]) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[1]). + Return(acc2) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[1], authtypes.FeeCollectorName, + sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 42))) + }, + expectedMinGasPrices: "1.000000000000000000stake", + expectedTxPriority: 1000000, + }, + { + name: "fail: enough fee with not enough funds", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[0]).Return(acc1) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[0], authtypes.FeeCollectorName, + sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 42))). + Return(errors.New("NOPE")) + }, + expectedError: "error deducting fee: NOPE: insufficient funds", + }, + { + name: "ok: enough fee with granter", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + txBuilder.SetFeeGranter(addrs[2]) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.FeeGrantKeeper.EXPECT().UseGrantedFees(gomock.Any(), addrs[2], + addrs[0], sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 42)), + gomock.Any(), + ) + m.AccountKeeper.EXPECT().GetAccount(gomock.Any(), addrs[2]). + Return(acc3) + m.BankKeeper.EXPECT().SendCoinsFromAccountToModule(gomock.Any(), + addrs[2], authtypes.FeeCollectorName, + sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 42))) + }, + expectedMinGasPrices: "1.000000000000000000stake", + expectedTxPriority: 1000000, + }, + { + name: "fail: enough fee with granter but feegrant disabled", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + txBuilder.SetFeeGranter(addrs[2]) + return txBuilder.GetTx() + }, + disableFeeGrant: true, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + }, + expectedError: "error deducting fee: fee grants are not enabled: invalid request", + }, + { + name: "fail: enough fee with granter but not granted", + tx: func() sdk.Tx { + txBuilder := txConfig.NewTxBuilder() + txBuilder.SetMsgs(testdata.NewTestMsg(addrs[0], addrs[1])) + txBuilder.SetGasLimit(42) + txBuilder.SetFeeAmount(sdk.NewCoins( + sdk.NewInt64Coin(types.DefaultFeeDenom, 42), + )) + txBuilder.SetFeeGranter(addrs[2]) + return txBuilder.GetTx() + }, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil).Times(2) + m.DynamicfeeKeeper.EXPECT().GetMinGasPrice(m.ctx, types.DefaultFeeDenom). + Return(sdk.NewInt64DecCoin(types.DefaultFeeDenom, 1), nil) + m.FeeGrantKeeper.EXPECT().UseGrantedFees(gomock.Any(), addrs[2], + addrs[0], sdk.NewCoins(sdk.NewInt64Coin(types.DefaultFeeDenom, 42)), + gomock.Any()). + Return(errors.New("NOPE")) + }, + expectedError: fmt.Sprintf( + "error deducting fee: %s does not allow to pay fees for %s: NOPE", + acc3.Address, acc1.Address), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + m = setupMocks(t) + nextInvoked bool + next = func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) { + nextInvoked = true + return ctx, nil + } + feeGrantKeeper ante.FeeGrantKeeper + ) + if !tt.disableFeeGrant { + feeGrantKeeper = m.FeeGrantKeeper + } + fmd := ante.NewDynamicfeeCheckDecorator(m.AccountKeeper, m.BankKeeper, feeGrantKeeper, m.DynamicfeeKeeper, nil) + if tt.genTx { + m.ctx = m.ctx.WithBlockHeight(0) + } else { + m.ctx = m.ctx.WithBlockHeight(1) + } + if tt.setup != nil { + tt.setup(m) + } + + newCtx, err := fmd.AnteHandle(m.ctx, tt.tx(), tt.simulate, next) + + if tt.expectedError != "" { + require.EqualError(t, err, tt.expectedError) + return + } + require.NoError(t, err) + assert.True(t, nextInvoked, "next is not invoked") + assert.EqualValues(t, tt.expectedConsumedGas, newCtx.GasMeter().GasConsumed()) + assert.Equal(t, tt.expectedMinGasPrices, newCtx.MinGasPrices().String(), "wrong min gas price") + assert.Equal(t, tt.expectedTxPriority, newCtx.Priority(), "wrong tx priority") + }) + } +} diff --git a/x/dynamicfee/client/cli/query.go b/x/dynamicfee/client/cli/query.go new file mode 100644 index 000000000000..5284d23d0d33 --- /dev/null +++ b/x/dynamicfee/client/cli/query.go @@ -0,0 +1,143 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// GetQueryCmd returns the parent command for all x/dynamicfee cli query commands. +func GetQueryCmd() *cobra.Command { + // create base command + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + // add sub-commands + cmd.AddCommand( + GetParamsCmd(), + GetStateCmd(), + GetGasPriceCmd(), + GetGasPricesCmd(), + ) + + return cmd +} + +// GetParamsCmd returns the cli-command that queries the current dynamicfee parameters. +func GetParamsCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "params", + Short: "Query for the current dynamicfee parameters", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + resp, err := queryClient.Params(cmd.Context(), &types.ParamsRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(&resp.Params) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetStateCmd returns the cli-command that queries the current dynamicfee state. +func GetStateCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "state", + Short: "Query for the current dynamicfee state", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + resp, err := queryClient.State(cmd.Context(), &types.StateRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(&resp.State) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetGasPriceCmd returns the cli-command that queries the current dynamicfee gas price. +func GetGasPriceCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "gas-price [denom]", + Short: "Query for the current dynamicfee gas price", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + resp, err := queryClient.GasPrice(cmd.Context(), &types.GasPriceRequest{ + Denom: args[0], + }) + if err != nil { + return err + } + + return clientCtx.PrintProto(resp) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetGasPricesCmd returns the cli-command that queries all current dynamicfee gas prices. +func GetGasPricesCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "gas-prices", + Short: "Query for all current dynamicfee gas prices", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, _ []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + queryClient := types.NewQueryClient(clientCtx) + resp, err := queryClient.GasPrices(cmd.Context(), &types.GasPricesRequest{}) + if err != nil { + return err + } + + return clientCtx.PrintProto(resp) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} diff --git a/x/dynamicfee/fuzz/aimd_eip1559_test.go b/x/dynamicfee/fuzz/aimd_eip1559_test.go new file mode 100644 index 000000000000..65811053bf98 --- /dev/null +++ b/x/dynamicfee/fuzz/aimd_eip1559_test.go @@ -0,0 +1,162 @@ +package fuzz_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + "cosmossdk.io/log" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// TestAIMDLearningRate ensures that the additive increase +// multiplicative decrease learning rate algorithm correctly +// adjusts the learning rate. In particular, if the block +// gas is greater than theta or less than 1 - theta, then +// the learning rate is increased by the additive increase +// parameter. Otherwise, the learning rate is decreased by +// the multiplicative decrease parameter. +func TestAIMDLearningRate(t *testing.T) { + t.Parallel() + + rapid.Check(t, func(t *rapid.T) { + state := types.DefaultAIMDState() + window := rapid.Int64Range(1, 50).Draw(t, "window") + state.Window = make([]uint64, window) + + params, maxBlockGas := CreateRandomAIMDParams(t) + + // Randomly generate the block gas. + numBlocks := rapid.Uint64Range(0, 1000).Draw(t, "num_blocks") + gasGen := rapid.Uint64Range(0, maxBlockGas) + + // Update the dynamic fee pricing. + for i := uint64(0); i < numBlocks; i++ { + blockGas := gasGen.Draw(t, "gas") + prevLearningRate := state.LearningRate + + // Update the dynamic fee pricing. + if err := state.Update(blockGas, maxBlockGas); err != nil { + t.Fatalf("block update errors: %v", err) + } + + // Update the learning rate. + lr := state.UpdateLearningRate(params, maxBlockGas) + avgGas := state.GetAverageGas(maxBlockGas) + + // Ensure that the learning rate is always bounded. + require.True(t, lr.GTE(params.MinLearningRate)) + require.True(t, lr.LTE(params.MaxLearningRate)) + + if avgGas.LTE(params.Gamma) || avgGas.GTE(math.LegacyOneDec().Sub(params.Gamma)) { + require.True(t, lr.GTE(prevLearningRate)) + } else { + require.True(t, lr.LTE(prevLearningRate)) + } + + // Update the current height. + state.IncrementHeight() + } + }) +} + +// TestAIMDGasPrice ensures that the additive increase multiplicative +// decrease gas price adjustment algorithm correctly adjusts the base +// fee. In particular, the gas price should function the same as the +// default EIP-1559 base fee adjustment algorithm. +func TestAIMDGasPrice(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + state := types.DefaultAIMDState() + window := rapid.Int64Range(1, 50).Draw(t, "window") + state.Window = make([]uint64, window) + + params, maxBlockGas := CreateRandomAIMDParams(t) + + // Randomly generate the block gas. + numBlocks := rapid.Uint64Range(0, uint64(window)*10).Draw(t, "num_blocks") + gasGen := rapid.Uint64Range(0, maxBlockGas) + + // Update the dynamic fee pricing. + for i := uint64(0); i < numBlocks; i++ { + blockGas := gasGen.Draw(t, "gas") + prevBaseGasPrice := state.BaseGasPrice + + if err := state.Update(blockGas, maxBlockGas); err != nil { + t.Fatalf("block update errors: %v", err) + } + + var total uint64 + for _, gas := range state.Window { + total += gas + } + + // Update the learning rate. + lr := state.UpdateLearningRate(params, maxBlockGas) + // Update the base gas price. + + var newPrice math.LegacyDec + func() { + defer func() { + if rec := recover(); rec != nil { + newPrice = params.MinBaseGasPrice + } + }() + + // Calculate the new base gasPrice with the learning rate adjustment. + currentBlockGas := math.LegacyNewDecFromInt(math.NewIntFromUint64(state.Window[state.Index])) + targetBlockGas := math.LegacyNewDecFromInt(math.NewIntFromUint64(types.GetTargetBlockGas(maxBlockGas, params))) + avgGas := (currentBlockGas.Sub(targetBlockGas)).Quo(targetBlockGas) + + // Truncate the learning rate adjustment to an integer. + // + // This is equivalent to + // 1 + (learningRate * (currentBlockGas - targetBlockGas) / targetBlockGas) + learningRateAdjustment := math.LegacyOneDec().Add(lr.Mul(avgGas)) + + // Update the base gasPrice. + newPrice = prevBaseGasPrice.Mul(learningRateAdjustment) + // Ensure the base gasPrice is greater than the minimum base gasPrice. + if newPrice.LT(params.MinBaseGasPrice) { + newPrice = params.MinBaseGasPrice + } + }() + + state.UpdateBaseGasPrice(log.NewNopLogger(), params, maxBlockGas) + + // Ensure that the minimum base fee is always less than the base gas price. + require.True(t, params.MinBaseGasPrice.LTE(state.BaseGasPrice)) + + require.Equal(t, newPrice, state.BaseGasPrice) + + // Update the current height. + state.IncrementHeight() + } + }) +} + +// CreateRandomAIMDParams returns a random set of parameters for the AIMD +// EIP-1559 dynamic fee pricing implementation. +func CreateRandomAIMDParams(t *rapid.T) (types.Params, uint64) { + a := rapid.Uint64Range(1, 1000).Draw(t, "alpha") + alpha := math.LegacyNewDec(int64(a)).Quo(math.LegacyNewDec(1000)) + + b := rapid.Uint64Range(50, 99).Draw(t, "beta") + beta := math.LegacyNewDec(int64(b)).Quo(math.LegacyNewDec(100)) + + g := rapid.Uint64Range(10, 50).Draw(t, "gamma") + gamma := math.LegacyNewDec(int64(g)).Quo(math.LegacyNewDec(100)) + + targetBlockGas := rapid.Uint64Range(1, 30_000_000).Draw(t, "target_block_gas") + maxBlockGas := rapid.Uint64Range(targetBlockGas, targetBlockGas*5).Draw(t, "max_block_gas") + + params := types.DefaultAIMDParams() + params.Alpha = alpha + params.Beta = beta + params.Gamma = gamma + params.TargetBlockUtilization = math.LegacyNewDecFromInt(math.NewIntFromUint64(targetBlockGas)).Quo(math.LegacyNewDecFromInt(math.NewIntFromUint64(maxBlockGas))) + + return params, maxBlockGas +} diff --git a/x/dynamicfee/fuzz/eip1559_test.go b/x/dynamicfee/fuzz/eip1559_test.go new file mode 100644 index 000000000000..6e9435dc2c05 --- /dev/null +++ b/x/dynamicfee/fuzz/eip1559_test.go @@ -0,0 +1,98 @@ +package fuzz_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + "cosmossdk.io/log" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// TestLearningRate ensures that the learning rate is always +// constant for the default EIP-1559 implementation. +func TestLearningRate(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + state := types.DefaultState() + params, maxBlockGas := CreateRandomParams(t) + + // Randomly generate alpha and beta. + prevLearningRate := state.LearningRate + + // Randomly generate the block gas. + blockGas := rapid.Uint64Range(0, maxBlockGas).Draw(t, "gas") + + // Update the dynamic fee pricing. + if err := state.Update(blockGas, maxBlockGas); err != nil { + t.Fatalf("block update errors: %v", err) + } + + // Update the learning rate. + lr := state.UpdateLearningRate(params, maxBlockGas) + require.Equal(t, types.DefaultMinLearningRate, lr) + require.Equal(t, prevLearningRate, state.LearningRate) + }) +} + +// TestGasPrice ensures that the gas price moves in the correct +// direction for the default EIP-1559 implementation. +func TestGasPrice(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + state := types.DefaultState() + params, maxBlockGas := CreateRandomParams(t) + + // Update the current base fee to be 10% higher than the minimum base fee. + prevBaseGasPrice := state.BaseGasPrice.Mul(math.LegacyNewDec(11)).Quo(math.LegacyNewDec(10)) + state.BaseGasPrice = prevBaseGasPrice + + // Randomly generate the block gas. + blockGas := rapid.Uint64Range(0, maxBlockGas).Draw(t, "gas") + + // Update the dynamic fee pricing. + if err := state.Update(blockGas, maxBlockGas); err != nil { + t.Fatalf("block update errors: %v", err) + } + + // Update the learning rate. + state.UpdateLearningRate(params, maxBlockGas) + // Update the base fee. + state.UpdateBaseGasPrice(log.NewNopLogger(), params, maxBlockGas) + + // Ensure that the minimum base fee is always less than the base fee. + require.True(t, params.MinBaseGasPrice.LTE(state.BaseGasPrice)) + + switch { + case blockGas > types.GetTargetBlockGas(maxBlockGas, params): + require.True(t, state.BaseGasPrice.GTE(prevBaseGasPrice)) + case blockGas < types.GetTargetBlockGas(maxBlockGas, params): + require.True(t, state.BaseGasPrice.LTE(prevBaseGasPrice)) + default: + require.Equal(t, state.BaseGasPrice, prevBaseGasPrice) + } + }) +} + +// CreateRandomParams returns a random set of parameters for the default +// EIP-1559 dynamic fee pricing implementation. +func CreateRandomParams(t *rapid.T) (types.Params, uint64) { + a := rapid.Uint64Range(1, 1000).Draw(t, "alpha") + alpha := math.LegacyNewDec(int64(a)).Quo(math.LegacyNewDec(1000)) + + b := rapid.Uint64Range(50, 99).Draw(t, "beta") + beta := math.LegacyNewDec(int64(b)).Quo(math.LegacyNewDec(100)) + + g := rapid.Uint64Range(10, 50).Draw(t, "gamma") + gamma := math.LegacyNewDec(int64(g)).Quo(math.LegacyNewDec(100)) + + maxBlockGas := rapid.Uint64Range(2, 30_000_000).Draw(t, "max_block_gas") + + params := types.DefaultParams() + params.Alpha = alpha + params.Beta = beta + params.Gamma = gamma + + return params, maxBlockGas +} diff --git a/x/dynamicfee/fuzz/tx_priority_test.go b/x/dynamicfee/fuzz/tx_priority_test.go new file mode 100644 index 000000000000..9ba833c1ba12 --- /dev/null +++ b/x/dynamicfee/fuzz/tx_priority_test.go @@ -0,0 +1,53 @@ +package fuzz_test + +import ( + "math" + "testing" + + "github.com/stretchr/testify/require" + "pgregory.net/rapid" + + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/ante" +) + +type input struct { + payFee sdk.Coin + gasLimit int64 + currentGasPrice sdk.DecCoin +} + +// TestGetTxPriority ensures that tx priority is properly bounded +func TestGetTxPriority(t *testing.T) { + rapid.Check(t, func(t *rapid.T) { + inputs := createRandomInput(t) + + priority := ante.GetTxPriority(inputs.payFee, inputs.gasLimit, inputs.currentGasPrice) + require.GreaterOrEqual(t, priority, int64(0)) + require.LessOrEqual(t, priority, int64(math.MaxInt64)) + }) +} + +// CreateRandomInput returns a random inputs to the priority function. +func createRandomInput(t *rapid.T) input { + denom := "skip" + + price := rapid.Int64Range(1, 1_000_000_000).Draw(t, "gas price") + priceDec := sdkmath.LegacyNewDecWithPrec(price, 6) + + gasLimit := rapid.Int64Range(1_000_000, 1_000_000_000_000).Draw(t, "gas limit") + + if priceDec.MulInt64(gasLimit).GTE(sdkmath.LegacyNewDec(math.MaxInt64)) { + t.Fatalf("not supposed to happen") + } + + payFeeAmt := rapid.Int64Range(priceDec.MulInt64(gasLimit).TruncateInt64(), math.MaxInt64).Draw(t, "fee amount") + + return input{ + payFee: sdk.NewCoin(denom, sdkmath.NewInt(payFeeAmt)), + gasLimit: gasLimit, + currentGasPrice: sdk.NewDecCoinFromDec(denom, priceDec), + } +} diff --git a/x/dynamicfee/keeper/abci.go b/x/dynamicfee/keeper/abci.go new file mode 100644 index 000000000000..5c904bbc1e8d --- /dev/null +++ b/x/dynamicfee/keeper/abci.go @@ -0,0 +1,15 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// 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 context.Context) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + return k.UpdateDynamicfee(sdkCtx) +} diff --git a/x/dynamicfee/keeper/dynamicfee.go b/x/dynamicfee/keeper/dynamicfee.go new file mode 100644 index 000000000000..da023fdc7b7a --- /dev/null +++ b/x/dynamicfee/keeper/dynamicfee.go @@ -0,0 +1,159 @@ +package keeper + +import ( + "context" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// UpdateDynamicfee updates the base fee and learning rate based on the +// AIMD learning rate adjustment algorithm. Note that if the dynamic fee +// pricing is disabled, this function will return without updating the +// dynamic fee pricing. 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) UpdateDynamicfee(ctx context.Context) error { + sdkCtx := sdk.UnwrapSDKContext(ctx) + logger := k.Logger(sdkCtx) + + params, err := k.GetParams(ctx) + if err != nil { + return err + } + + logger.Info( + "updated the dynamic fee pricing", + "params", params, + ) + + if !params.Enabled { + return nil + } + + maxBlockGas := k.GetMaxBlockGas(ctx, params) + + state, err := k.GetState(ctx) + if err != nil { + return err + } + + // Update the learning rate based on the block gas seen in the + // current block. This is the AIMD learning rate adjustment algorithm. + newLR := state.UpdateLearningRate(params, maxBlockGas) + + // Update the base gas price based with the new learning rate. + newBaseGasPrice := state.UpdateBaseGasPrice(logger, params, maxBlockGas) + + logger.Info( + "updated the dynamic fee pricing", + "height", sdkCtx.BlockHeight(), + "new_base_gas_price", newBaseGasPrice, + "new_learning_rate", newLR, + "average_block_gas", state.GetAverageGas(maxBlockGas), + "net_block_gas", state.GetNetGas(maxBlockGas, params), + ) + + // Increment the height of the state and set the new state. + state.IncrementHeight() + return k.SetState(ctx, state) +} + +// GetMaxBlockGas returns the maximum gas of a block +// It returns the value obtained from ConsensusParams if +// it is different from 0 or -1, otherwise it returns +// DefaultMaxBlockGas +func (k *Keeper) GetMaxBlockGas(ctx context.Context, params types.Params) uint64 { + sdkCtx := sdk.UnwrapSDKContext(ctx) + maxBlockGas := sdkCtx.ConsensusParams().Block.GetMaxGas() + if maxBlockGas == 0 || maxBlockGas == -1 { + return params.DefaultMaxBlockGas + } + return uint64(maxBlockGas) +} + +// GetBaseGasPrice returns the base fee from the dynamic fee pricing state. +func (k *Keeper) GetBaseGasPrice(ctx context.Context) (math.LegacyDec, error) { + state, err := k.GetState(ctx) + if err != nil { + return math.LegacyDec{}, err + } + + return state.BaseGasPrice, nil +} + +// GetLearningRate returns the learning rate from the dynamic fee pricing state. +func (k *Keeper) GetLearningRate(ctx context.Context) (math.LegacyDec, error) { + state, err := k.GetState(ctx) + if err != nil { + return math.LegacyDec{}, err + } + + return state.LearningRate, nil +} + +// GetMinGasPrice returns the mininum gas prices for given denom as +// sdk.DecCoins from the dynamic fee pricing state. +func (k *Keeper) GetMinGasPrice(ctx context.Context, denom string) (sdk.DecCoin, error) { + baseGasPrice, err := k.GetBaseGasPrice(ctx) + if err != nil { + return sdk.DecCoin{}, err + } + + params, err := k.GetParams(ctx) + if err != nil { + return sdk.DecCoin{}, err + } + + var gasPrice sdk.DecCoin + + if params.FeeDenom == denom { + gasPrice = sdk.NewDecCoinFromDec(params.FeeDenom, baseGasPrice) + } else { + gasPrice, err = k.ResolveToDenom(ctx, sdk.NewDecCoinFromDec(params.FeeDenom, baseGasPrice), denom) + if err != nil { + return sdk.DecCoin{}, err + } + } + + return gasPrice, nil +} + +// GetMinGasPrices returns the mininum gas prices as sdk.DecCoins from the +// dynamic fee pricing state. +func (k *Keeper) GetMinGasPrices(ctx context.Context) (sdk.DecCoins, error) { + baseGasPrice, err := k.GetBaseGasPrice(ctx) + if err != nil { + return sdk.NewDecCoins(), err + } + + params, err := k.GetParams(ctx) + if err != nil { + return sdk.NewDecCoins(), err + } + + minGasPrice := sdk.NewDecCoinFromDec(params.FeeDenom, baseGasPrice) + minGasPrices := sdk.NewDecCoins(minGasPrice) + + extraDenoms, err := k.resolver.ExtraDenoms(ctx) + if err != nil { + return sdk.NewDecCoins(), err + } + + sdkCtx := sdk.UnwrapSDKContext(ctx) + for _, denom := range extraDenoms { + gasPrice, err := k.ResolveToDenom(ctx, minGasPrice, denom) + if err != nil { + k.Logger(sdkCtx).Info( + "failed to convert gas price", + "min gas price", minGasPrice, + "denom", denom, + ) + continue + } + minGasPrices = minGasPrices.Add(gasPrice) + } + + return minGasPrices, nil +} diff --git a/x/dynamicfee/keeper/dynamicfee_test.go b/x/dynamicfee/keeper/dynamicfee_test.go new file mode 100644 index 000000000000..3816aaa573bc --- /dev/null +++ b/x/dynamicfee/keeper/dynamicfee_test.go @@ -0,0 +1,526 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestUpdateDynamicfee(t *testing.T) { + t.Run("empty block with default eip1559 with min base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + params := types.DefaultParams() + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, params.MinBaseGasPrice) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("empty block with default eip1559 with preset base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + params := types.DefaultParams() + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to decrease by 1/8th. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + factor := math.LegacyMustNewDecFromStr("0.875") + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("empty block with default eip1559 with preset base fee < 1", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + // this value should be ignored -> 0.5 should be used instead + state.BaseGasPrice = math.LegacyMustNewDecFromStr("0.25") + + // change MinBaseGasPrice value < 1 + params := types.DefaultParams() + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("0.5") + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + require.Equal(fee, math.LegacyMustNewDecFromStr("0.5")) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("empty block default eip1559 with preset base fee that should default to min", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + // Set the base fee to just below the expected threshold decrease of 1/8th. This means it + // should default to the minimum base fee. + state := types.DefaultState() + factor := math.LegacyMustNewDecFromStr("0.125") + change := state.BaseGasPrice.Mul(factor) + state.BaseGasPrice = types.DefaultMinBaseGasPrice.Sub(change) + + params := types.DefaultParams() + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to decrease by 1/8th. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, params.MinBaseGasPrice) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("target block with default eip1559 at min base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + params := types.DefaultParams() + + // Reaching the target block gas means that we expect this to not + // increase. + err := state.Update(types.GetTargetBlockGas(testutil.MaxBlockGas, params), testutil.MaxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to remain the same. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, params.MinBaseGasPrice) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("target block with default eip1559 at preset base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + params := types.DefaultParams() + + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + // Reaching the target block gas means that we expect this to not + // increase. + err := state.Update(types.GetTargetBlockGas(testutil.MaxBlockGas, params), testutil.MaxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to remain the same. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(state.BaseGasPrice, fee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("max block with default eip1559 at min base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + params := types.DefaultParams() + + // Reaching the target block gas means that we expect this to not + // increase. + err := state.Update(testutil.MaxBlockGas, testutil.MaxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to increase by 1/8th. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + factor := math.LegacyMustNewDecFromStr("1.125") + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("max block with default eip1559 at preset base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultState() + params := types.DefaultParams() + + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + // Reaching the target block gas means that we expect this to not + // increase. + err := state.Update(testutil.MaxBlockGas, testutil.MaxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to increase by 1/8th. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + factor := math.LegacyMustNewDecFromStr("1.125") + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("in-between min and target block with default eip1559 at min base fee", func(t *testing.T) { + require := require.New(t) + maxBlockGas := uint64(100) + k, ctx := testutil.SetupKeeper(t, maxBlockGas) + state := types.DefaultState() + params := types.DefaultParams() + + err := state.Update(25, maxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to remain the same since it is at min base fee. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, params.MinBaseGasPrice) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("in-between min and target block with default eip1559 at preset base fee", func(t *testing.T) { + require := require.New(t) + maxBlockGas := uint64(100) + k, ctx := testutil.SetupKeeper(t, maxBlockGas) + state := types.DefaultState() + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + + params := types.DefaultParams() + err := state.Update(25, maxBlockGas) + + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to decrease by 1/8th * 1/2. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + factor := math.LegacyMustNewDecFromStr("0.9375") + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("in-between target and max block with default eip1559 at min base fee", func(t *testing.T) { + require := require.New(t) + maxBlockGas := uint64(100) + k, ctx := testutil.SetupKeeper(t, maxBlockGas) + state := types.DefaultState() + params := types.DefaultParams() + + err := state.Update(75, maxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to increase by 1/8th * 1/2. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + factor := math.LegacyMustNewDecFromStr("1.0625") + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("in-between target and max block with default eip1559 at preset base fee", func(t *testing.T) { + require := require.New(t) + maxBlockGas := uint64(100) + k, ctx := testutil.SetupKeeper(t, maxBlockGas) + state := types.DefaultState() + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + params := types.DefaultParams() + + err := state.Update(75, maxBlockGas) + require.NoError(err) + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to increase by 1/8th * 1/2. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + + factor := math.LegacyMustNewDecFromStr("1.0625") + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(math.LegacyMustNewDecFromStr("0.125"), lr) + }) + + t.Run("empty blocks with aimd eip1559 with min base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, params.MinBaseGasPrice) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + expectedLR := state.LearningRate.Add(params.Alpha) + require.Equal(expectedLR, lr) + }) + + t.Run("empty blocks with aimd eip1559 with preset base fee", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultAIMDState() + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + params := types.DefaultAIMDParams() + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to decrease by 1/8th and the learning rate to + // increase by alpha. + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + expectedLR := state.LearningRate.Add(params.Alpha) + require.Equal(expectedLR, lr) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + factor := math.LegacyOneDec().Add(math.LegacyMustNewDecFromStr("-1.0").Mul(lr)) + expectedFee := state.BaseGasPrice.Mul(factor) + require.Equal(fee, expectedFee) + }) + + t.Run("empty blocks aimd eip1559 with preset base fee that should default to min", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + params := types.DefaultAIMDParams() + + state := types.DefaultAIMDState() + lr := math.LegacyMustNewDecFromStr("0.125") + increase := state.BaseGasPrice.Mul(lr) + + state.BaseGasPrice = types.DefaultMinBaseGasPrice.Add(increase) + state.LearningRate = lr + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + expectedLR := state.LearningRate.Add(params.Alpha) + require.Equal(expectedLR, lr) + + // We expect the base fee to decrease by 1/8th and the learning rate to + // increase by alpha. + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, params.MinBaseGasPrice) + }) + + t.Run("target block with aimd eip1559 at min base fee + LR", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + // Reaching the target block gas means that we expect this to not + // increase. + for i := 0; i < len(state.Window); i++ { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to remain the same and the learning rate to + // remain at minimum. + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(params.MinLearningRate, lr) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(state.BaseGasPrice, fee) + }) + + t.Run("target block with aimd eip1559 at preset base fee + LR", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + state := types.DefaultAIMDState() + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(2)) + state.LearningRate = math.LegacyMustNewDecFromStr("0.125") + params := types.DefaultAIMDParams() + + // Reaching the target block gas means that we expect this to not + // increase. + for i := 0; i < len(state.Window); i++ { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + + k.InitGenesis(ctx, types.GenesisState{Params: params, State: state}) + + require.NoError(k.UpdateDynamicfee(ctx)) + + // We expect the base fee to decrease by 1/8th and the learning rate to + // decrease by lr * beta. + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + expectedLR := state.LearningRate.Mul(params.Beta) + require.Equal(expectedLR, lr) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(state.BaseGasPrice, fee) + }) +} + +func TestGetBaseGasPrice(t *testing.T) { + t.Run("can retrieve base fee with default eip-1559", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + gs := types.DefaultGenesisState() + k.InitGenesis(ctx, *gs) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, gs.State.BaseGasPrice) + }) + + t.Run("can retrieve base fee with aimd eip-1559", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + gs := types.DefaultAIMDGenesisState() + k.InitGenesis(ctx, *gs) + + fee, err := k.GetBaseGasPrice(ctx) + require.NoError(err) + require.Equal(fee, gs.State.BaseGasPrice) + }) +} + +func TestGetLearningRate(t *testing.T) { + t.Run("can retrieve learning rate with default eip-1559", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + gs := types.DefaultGenesisState() + k.InitGenesis(ctx, *gs) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(lr, gs.State.LearningRate) + }) + + t.Run("can retrieve learning rate with aimd eip-1559", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + gs := types.DefaultAIMDGenesisState() + k.InitGenesis(ctx, *gs) + + lr, err := k.GetLearningRate(ctx) + require.NoError(err) + require.Equal(lr, gs.State.LearningRate) + }) +} + +func TestGetMinGasPrices(t *testing.T) { + t.Run("can retrieve min gas prices with default eip-1559", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + gs := types.DefaultGenesisState() + k.InitGenesis(ctx, *gs) + + expected := sdk.NewDecCoins(sdk.NewDecCoinFromDec(types.DefaultFeeDenom, gs.State.BaseGasPrice)) + + mgp, err := k.GetMinGasPrices(ctx) + require.NoError(err) + require.Equal(expected, mgp) + }) + + t.Run("can retrieve min gas prices with aimd eip-1559", func(t *testing.T) { + require := require.New(t) + k, ctx := testutil.SetupKeeper(t, 0) + gs := types.DefaultAIMDGenesisState() + k.InitGenesis(ctx, *gs) + + expected := sdk.NewDecCoins(sdk.NewDecCoinFromDec(types.DefaultFeeDenom, gs.State.BaseGasPrice)) + + mgp, err := k.GetMinGasPrices(ctx) + require.NoError(err) + require.Equal(expected, mgp) + }) +} diff --git a/x/dynamicfee/keeper/genesis.go b/x/dynamicfee/keeper/genesis.go new file mode 100644 index 000000000000..00d06d977f3b --- /dev/null +++ b/x/dynamicfee/keeper/genesis.go @@ -0,0 +1,46 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// InitGenesis initializes the dynamicfee module's state from a given genesis state. +func (k *Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) { + if err := gs.ValidateBasic(); err != nil { + panic(err) + } + + if gs.Params.Window != uint64(len(gs.State.Window)) { + panic("genesis state and parameters do not match for window") + } + + // Initialize the dynamic fee pricing state and parameters. + if err := k.SetParams(ctx, gs.Params); err != nil { + panic(err) + } + + if err := k.SetState(ctx, gs.State); err != nil { + panic(err) + } + + // always init enabled height to -1 until it is explicitly set later in the application + k.SetEnabledHeight(ctx, -1) +} + +// ExportGenesis returns a GenesisState for a given context. +func (k *Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + // Get the dynamicfee module's parameters. + params, err := k.GetParams(ctx) + if err != nil { + panic(err) + } + + // Get the dynamicfee module's state. + state, err := k.GetState(ctx) + if err != nil { + panic(err) + } + + return types.NewGenesisState(params, state) +} diff --git a/x/dynamicfee/keeper/genesis_test.go b/x/dynamicfee/keeper/genesis_test.go new file mode 100644 index 000000000000..c21118a81271 --- /dev/null +++ b/x/dynamicfee/keeper/genesis_test.go @@ -0,0 +1,69 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestInitGenesis(t *testing.T) { + k, ctx := testutil.SetupKeeper(t, 0) + t.Run("default genesis should not panic", func(t *testing.T) { + require.NotPanics(t, func() { + k.InitGenesis(ctx, *types.DefaultGenesisState()) + }) + }) + + t.Run("default AIMD genesis should not panic", func(t *testing.T) { + require.NotPanics(t, func() { + k.InitGenesis(ctx, *types.DefaultAIMDGenesisState()) + }) + }) + + t.Run("bad genesis state should panic", func(t *testing.T) { + gs := types.DefaultGenesisState() + gs.Params.Window = 0 + require.Panics(t, func() { + k.InitGenesis(ctx, *gs) + }) + }) + + t.Run("mismatch in params and state for window should panic", func(t *testing.T) { + gs := types.DefaultAIMDGenesisState() + gs.Params.Window = 1 + + require.Panics(t, func() { + k.InitGenesis(ctx, *gs) + }) + }) +} + +func TestExportGenesis(t *testing.T) { + k, ctx := testutil.SetupKeeper(t, 0) + t.Run("export genesis should not panic for default eip-1559", func(t *testing.T) { + gs := types.DefaultGenesisState() + k.InitGenesis(ctx, *gs) + + var exportedGenesis *types.GenesisState + require.NotPanics(t, func() { + exportedGenesis = k.ExportGenesis(ctx) + }) + + require.Equal(t, gs, exportedGenesis) + }) + + t.Run("export genesis should not panic for default AIMD eip-1559", func(t *testing.T) { + gs := types.DefaultAIMDGenesisState() + k.InitGenesis(ctx, *gs) + + var exportedGenesis *types.GenesisState + require.NotPanics(t, func() { + exportedGenesis = k.ExportGenesis(ctx) + }) + + require.Equal(t, gs, exportedGenesis) + }) +} diff --git a/x/dynamicfee/keeper/keeper.go b/x/dynamicfee/keeper/keeper.go new file mode 100644 index 000000000000..ac2672275ba0 --- /dev/null +++ b/x/dynamicfee/keeper/keeper.go @@ -0,0 +1,154 @@ +package keeper + +import ( + "context" + "fmt" + "strconv" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// Keeper is the x/dynamicfee keeper. +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + resolver types.DenomResolver + + // The address that is capable of executing a MsgParams message. + // Typically, this will be the governance module's address. + authority string +} + +// NewKeeper constructs a new dynamicfee keeper. +func NewKeeper( + cdc codec.BinaryCodec, + storeKey storetypes.StoreKey, + resolver types.DenomResolver, + authority string, +) *Keeper { + if _, err := sdk.AccAddressFromBech32(authority); err != nil { + panic(fmt.Sprintf("invalid authority address: %s", authority)) + } + + k := &Keeper{ + cdc: cdc, + storeKey: storeKey, + resolver: resolver, + authority: authority, + } + + return k +} + +// Logger returns a dynamicfee module-specific logger. +func (k *Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+types.ModuleName) +} + +// GetAuthority returns the address that is capable of executing a MsgUpdateParams message. +func (k *Keeper) GetAuthority() string { + return k.authority +} + +// GetEnabledHeight returns the height at which the dynamicfee was enabled. +func (k *Keeper) GetEnabledHeight(ctx context.Context) (int64, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + store := sdkCtx.KVStore(k.storeKey) + + key := types.KeyEnabledHeight + bz := store.Get(key) + if bz == nil { + return -1, nil + } + + return strconv.ParseInt(string(bz), 10, 64) +} + +// SetEnabledHeight sets the height at which the dynamicfee was enabled. +func (k *Keeper) SetEnabledHeight(ctx context.Context, height int64) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + + store := sdkCtx.KVStore(k.storeKey) + + bz := []byte(strconv.FormatInt(height, 10)) + + store.Set(types.KeyEnabledHeight, bz) +} + +// ResolveToDenom converts the given coin to the given denomination. +func (k *Keeper) ResolveToDenom(ctx context.Context, coin sdk.DecCoin, denom string) (sdk.DecCoin, error) { + if k.resolver == nil { + return sdk.DecCoin{}, types.ErrResolverNotSet + } + + return k.resolver.ConvertToDenom(ctx, coin, denom) +} + +// SetDenomResolver sets the keeper's denom resolver. +func (k *Keeper) SetDenomResolver(resolver types.DenomResolver) { + k.resolver = resolver +} + +// GetState returns the dynamicfee module's state. +func (k *Keeper) GetState(ctx context.Context) (types.State, error) { + store := sdk.UnwrapSDKContext(ctx).KVStore(k.storeKey) + + key := types.KeyState + bz := store.Get(key) + + state := types.State{} + if err := state.Unmarshal(bz); err != nil { + return types.State{}, err + } + + return state, nil +} + +// SetState sets the dynamicfee module's state. +func (k *Keeper) SetState(ctx context.Context, state types.State) error { + store := sdk.UnwrapSDKContext(ctx).KVStore(k.storeKey) + + bz, err := state.Marshal() + if err != nil { + return err + } + + store.Set(types.KeyState, bz) + + return nil +} + +// GetParams returns the dynamicfee module's parameters. +func (k *Keeper) GetParams(ctx context.Context) (types.Params, error) { + store := sdk.UnwrapSDKContext(ctx).KVStore(k.storeKey) + + key := types.KeyParams + bz := store.Get(key) + + params := types.Params{} + if err := params.Unmarshal(bz); err != nil { + return types.Params{}, err + } + + return params, nil +} + +// SetParams sets the dynamicfee module's parameters. +func (k *Keeper) SetParams(ctx context.Context, params types.Params) error { + store := sdk.UnwrapSDKContext(ctx).KVStore(k.storeKey) + + bz, err := params.Marshal() + if err != nil { + return err + } + + store.Set(types.KeyParams, bz) + + return nil +} diff --git a/x/dynamicfee/keeper/keeper_test.go b/x/dynamicfee/keeper/keeper_test.go new file mode 100644 index 000000000000..e263337515c8 --- /dev/null +++ b/x/dynamicfee/keeper/keeper_test.go @@ -0,0 +1,127 @@ +package keeper_test + +import ( + "testing" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestState(t *testing.T) { + k, ctx := testutil.SetupKeeper(t, 0) + t.Run("set and get default eip1559 state", func(t *testing.T) { + state := types.DefaultState() + + err := k.SetState(ctx, state) + require.NoError(t, err) + + gotState, err := k.GetState(ctx) + require.NoError(t, err) + + require.Equal(t, state, gotState) + }) + + t.Run("set and get aimd eip1559 state", func(t *testing.T) { + state := types.DefaultAIMDState() + + err := k.SetState(ctx, state) + require.NoError(t, err) + + gotState, err := k.GetState(ctx) + require.NoError(t, err) + + require.Equal(t, state, gotState) + }) +} + +func TestParams(t *testing.T) { + k, ctx := testutil.SetupKeeper(t, 0) + t.Run("set and get default params", func(t *testing.T) { + params := types.DefaultParams() + + err := k.SetParams(ctx, params) + require.NoError(t, err) + + gotParams, err := k.GetParams(ctx) + require.NoError(t, err) + + require.Equal(t, params, gotParams) + }) + + t.Run("set and get custom params", func(t *testing.T) { + params := types.Params{ + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyNewDec(10), + TargetBlockUtilization: math.LegacyMustNewDecFromStr("0.1"), + MinLearningRate: math.LegacyMustNewDecFromStr("0.1"), + MaxLearningRate: math.LegacyMustNewDecFromStr("0.1"), + Window: 1, + Enabled: true, + } + + err := k.SetParams(ctx, params) + require.NoError(t, err) + + gotParams, err := k.GetParams(ctx) + require.NoError(t, err) + + require.Equal(t, params, gotParams) + }) +} + +func TestEnabledHeight(t *testing.T) { + k, ctx := testutil.SetupKeeper(t, 0) + t.Run("get and set values", func(t *testing.T) { + k.SetEnabledHeight(ctx, 10) + + got, err := k.GetEnabledHeight(ctx) + require.NoError(t, err) + require.Equal(t, int64(10), got) + }) +} + +func TestGetMaxBlockGas(t *testing.T) { + k, ctx := testutil.SetupKeeper(t, 0) + t.Run("get max block gas when 0", func(t *testing.T) { + ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: int64(0)}}) + params := types.DefaultParams() + + err := k.SetParams(ctx, params) + require.NoError(t, err) + + maxGas := k.GetMaxBlockGas(ctx, params) + + require.Equal(t, uint64(100_000_000), maxGas) + }) + + t.Run("get max block gas when -1", func(t *testing.T) { + ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: int64(-1)}}) + params := types.DefaultParams() + + err := k.SetParams(ctx, params) + require.NoError(t, err) + + maxGas := k.GetMaxBlockGas(ctx, params) + + require.Equal(t, uint64(100_000_000), maxGas) + }) + + t.Run("get max block gas", func(t *testing.T) { + ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: int64(42)}}) + params := types.DefaultParams() + + err := k.SetParams(ctx, params) + require.NoError(t, err) + + maxGas := k.GetMaxBlockGas(ctx, params) + + require.Equal(t, uint64(42), maxGas) + }) +} diff --git a/x/dynamicfee/keeper/msg_server.go b/x/dynamicfee/keeper/msg_server.go new file mode 100644 index 000000000000..ac41cdec18e8 --- /dev/null +++ b/x/dynamicfee/keeper/msg_server.go @@ -0,0 +1,53 @@ +package keeper + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +var _ types.MsgServer = (*MsgServer)(nil) + +// MsgServer is the server API for x/dynamicfee Msg service. +type MsgServer struct { + k *Keeper +} + +// NewMsgServer returns the MsgServer implementation. +func NewMsgServer(k *Keeper) types.MsgServer { + return &MsgServer{k} +} + +// Params defines a method that updates the module's parameters. The signer of the message must +// be the module authority. +func (ms MsgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + if msg.Authority != ms.k.GetAuthority() { + return nil, fmt.Errorf("invalid authority to execute message") + } + + gotParams, err := ms.k.GetParams(ctx) + if err != nil { + return nil, fmt.Errorf("error getting params: %w", err) + } + + // if going from disabled -> enabled, set enabled height + if !gotParams.Enabled && msg.Params.Enabled { + ms.k.SetEnabledHeight(ctx, ctx.BlockHeight()) + } + + params := msg.Params + if err := ms.k.SetParams(ctx, params); err != nil { + return nil, fmt.Errorf("error setting params: %w", err) + } + + newState := types.NewState(params.Window, params.MinBaseGasPrice, params.MinLearningRate) + if err := ms.k.SetState(ctx, newState); err != nil { + return nil, fmt.Errorf("error setting state: %w", err) + } + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/x/dynamicfee/keeper/msg_server_test.go b/x/dynamicfee/keeper/msg_server_test.go new file mode 100644 index 000000000000..82faadad24f8 --- /dev/null +++ b/x/dynamicfee/keeper/msg_server_test.go @@ -0,0 +1,129 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +func TestMsgUpdateParams(t *testing.T) { + authority := authtypes.NewModuleAddress(govtypes.ModuleName).String() + t.Run("accepts a req with no params", func(t *testing.T) { + require := require.New(t) + msgServer, k, ctx := testutil.SetupMsgServer(t, 0) + req := &types.MsgUpdateParams{ + Authority: authority, + } + resp, err := msgServer.UpdateParams(ctx, req) + require.NoError(err) + require.NotNil(resp) + + params, err := k.GetParams(ctx) + require.NoError(err) + require.False(params.Enabled) + }) + + t.Run("accepts a req with params", func(t *testing.T) { + require := require.New(t) + msgServer, k, ctx := testutil.SetupMsgServer(t, 0) + req := &types.MsgUpdateParams{ + Authority: authority, + Params: types.DefaultParams(), + } + resp, err := msgServer.UpdateParams(ctx, req) + require.NoError(err) + require.NotNil(resp) + + params, err := k.GetParams(ctx) + require.NoError(err) + require.Equal(req.Params, params) + }) + + t.Run("rejects a req with invalid signer", func(t *testing.T) { + require := require.New(t) + msgServer, _, ctx := testutil.SetupMsgServer(t, 0) + req := &types.MsgUpdateParams{ + Authority: "invalid", + } + _, err := msgServer.UpdateParams(ctx, req) + require.Error(err) + }) + + t.Run("sets enabledHeight when transitioning from disabled -> enabled", func(t *testing.T) { + require := require.New(t) + msgServer, k, ctx := testutil.SetupMsgServer(t, 0) + ctx = ctx.WithBlockHeight(ctx.BlockHeight()) + enabledParams := types.DefaultParams() + + req := &types.MsgUpdateParams{ + Authority: authority, + Params: enabledParams, + } + _, err := msgServer.UpdateParams(ctx, req) + require.NoError(err) + + disableParams := types.DefaultParams() + disableParams.Enabled = false + + req = &types.MsgUpdateParams{ + Authority: authority, + Params: disableParams, + } + _, err = msgServer.UpdateParams(ctx, req) + require.NoError(err) + + gotHeight, err := k.GetEnabledHeight(ctx) + require.NoError(err) + require.Equal(ctx.BlockHeight(), gotHeight) + + // now that dynamic fees are disabled, enable and check block height + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 10) + + req = &types.MsgUpdateParams{ + Authority: authority, + Params: enabledParams, + } + _, err = msgServer.UpdateParams(ctx, req) + require.NoError(err) + + newHeight, err := k.GetEnabledHeight(ctx) + require.NoError(err) + require.Equal(ctx.BlockHeight(), newHeight) + }) + + t.Run("resets state after new params request", func(t *testing.T) { + require := require.New(t) + msgServer, k, ctx := testutil.SetupMsgServer(t, 0) + params, err := k.GetParams(ctx) + require.NoError(err) + err = k.SetState(ctx, types.DefaultState()) + require.NoError(err) + + state, err := k.GetState(ctx) + require.NoError(err) + + err = state.Update(testutil.MaxBlockGas, testutil.MaxBlockGas) + require.NoError(err) + + err = k.SetState(ctx, state) + require.NoError(err) + + params.Window = 100 + req := &types.MsgUpdateParams{ + Authority: authority, + Params: params, + } + _, err = msgServer.UpdateParams(ctx, req) + require.NoError(err) + + state, err = k.GetState(ctx) + require.NoError(err) + require.Equal(params.Window, uint64(len(state.Window))) + require.Equal(state.Window[0], uint64(0)) + }) +} diff --git a/x/dynamicfee/keeper/query_server.go b/x/dynamicfee/keeper/query_server.go new file mode 100644 index 000000000000..1f0aa94e8cb1 --- /dev/null +++ b/x/dynamicfee/keeper/query_server.go @@ -0,0 +1,52 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +var _ types.QueryServer = (*QueryServer)(nil) + +// QueryServer defines the gRPC server for the x/dynamicfee module. +type QueryServer struct { + k Keeper +} + +// NewQueryServer creates a new instance of the x/dynamicfee QueryServer type. +func NewQueryServer(keeper Keeper) types.QueryServer { + return &QueryServer{k: keeper} +} + +// Params defines a method that returns the current dynamicfee parameters. +func (q QueryServer) Params(goCtx context.Context, _ *types.ParamsRequest) (*types.ParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + params, err := q.k.GetParams(ctx) + return &types.ParamsResponse{Params: params}, err +} + +// State defines a method that returns the current dynamicfee state. +func (q QueryServer) State(goCtx context.Context, _ *types.StateRequest) (*types.StateResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + state, err := q.k.GetState(ctx) + return &types.StateResponse{State: state}, err +} + +// GasPrice defines a method that returns the current dynamicfee base gas price. +func (q QueryServer) GasPrice(goCtx context.Context, req *types.GasPriceRequest) (*types.GasPriceResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + gasPrice, err := q.k.GetMinGasPrice(ctx, req.GetDenom()) + return &types.GasPriceResponse{Price: gasPrice}, err +} + +// GasPrices defines a method that returns the current dynamicfee list of gas prices. +func (q QueryServer) GasPrices(goCtx context.Context, _ *types.GasPricesRequest) (*types.GasPricesResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + gasPrices, err := q.k.GetMinGasPrices(ctx) + return &types.GasPricesResponse{Prices: gasPrices}, err +} diff --git a/x/dynamicfee/keeper/query_server_test.go b/x/dynamicfee/keeper/query_server_test.go new file mode 100644 index 000000000000..6df18384f778 --- /dev/null +++ b/x/dynamicfee/keeper/query_server_test.go @@ -0,0 +1,185 @@ +package keeper_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestParamsRequest(t *testing.T) { + t.Run("can get default params", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + err := k.SetParams(ctx, types.DefaultParams()) + require.NoError(err) + req := &types.ParamsRequest{} + resp, err := queryServer.Params(ctx, req) + require.NoError(err) + require.NotNil(resp) + + require.Equal(types.DefaultParams(), resp.Params) + + params, err := k.GetParams(ctx) + require.NoError(err) + + require.Equal(resp.Params, params) + }) + + t.Run("can get updated params", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + params := types.Params{ + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyNewDec(10), + TargetBlockUtilization: math.LegacyMustNewDecFromStr("0.1"), + MinLearningRate: math.LegacyMustNewDecFromStr("0.1"), + MaxLearningRate: math.LegacyMustNewDecFromStr("0.1"), + Window: 1, + Enabled: true, + } + err := k.SetParams(ctx, params) + require.NoError(err) + + req := &types.ParamsRequest{} + resp, err := queryServer.Params(ctx, req) + require.NoError(err) + require.NotNil(resp) + + require.Equal(params, resp.Params) + + params, err = k.GetParams(ctx) + require.NoError(err) + + require.Equal(resp.Params, params) + }) +} + +func TestStateRequest(t *testing.T) { + t.Run("can get default state", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + err := k.SetState(ctx, types.DefaultState()) + require.NoError(err) + req := &types.StateRequest{} + resp, err := queryServer.State(ctx, req) + require.NoError(err) + require.NotNil(resp) + + require.Equal(types.DefaultState(), resp.State) + + state, err := k.GetState(ctx) + require.NoError(err) + + require.Equal(resp.State, state) + }) + + t.Run("can get updated state", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + state := types.State{ + BaseGasPrice: math.LegacyOneDec(), + LearningRate: math.LegacyOneDec(), + Window: []uint64{1}, + Index: 0, + } + err := k.SetState(ctx, state) + require.NoError(err) + + req := &types.StateRequest{} + resp, err := queryServer.State(ctx, req) + require.NoError(err) + require.NotNil(resp) + + require.Equal(state, resp.State) + + state, err = k.GetState(ctx) + require.NoError(err) + + require.Equal(resp.State, state) + }) +} + +func TestGasPriceRequest(t *testing.T) { + t.Run("can get gas price", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + err := k.SetParams(ctx, types.DefaultParams()) + require.NoError(err) + err = k.SetState(ctx, types.DefaultState()) + require.NoError(err) + req := &types.GasPriceRequest{ + Denom: types.DefaultFeeDenom, + } + resp, err := queryServer.GasPrice(ctx, req) + require.NoError(err) + require.NotNil(resp) + + gasPrice, err := k.GetMinGasPrice(ctx, req.GetDenom()) + require.NoError(err) + + require.Equal(resp.GetPrice(), gasPrice) + }) + + t.Run("can get updated gas price", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + state := types.State{ + BaseGasPrice: math.LegacyOneDec(), + } + err := k.SetState(ctx, state) + require.NoError(err) + + params := types.Params{ + FeeDenom: "test", + } + err = k.SetParams(ctx, params) + require.NoError(err) + + req := &types.GasPriceRequest{ + Denom: "test", + } + resp, err := queryServer.GasPrice(ctx, req) + require.NoError(err) + require.NotNil(resp) + + gasPrice, err := k.GetMinGasPrice(ctx, req.GetDenom()) + require.NoError(err) + + require.Equal(resp.GetPrice(), gasPrice) + }) + + t.Run("can get updated gas price < 1", func(t *testing.T) { + require := require.New(t) + queryServer, k, ctx := testutil.SetupQueryServer(t, 0) + state := types.State{ + BaseGasPrice: math.LegacyMustNewDecFromStr("0.005"), + } + err := k.SetState(ctx, state) + require.NoError(err) + + params := types.Params{ + FeeDenom: "test", + } + err = k.SetParams(ctx, params) + require.NoError(err) + + req := &types.GasPriceRequest{ + Denom: "test", + } + resp, err := queryServer.GasPrice(ctx, req) + require.NoError(err) + require.NotNil(resp) + + fee, err := k.GetMinGasPrice(ctx, req.GetDenom()) + require.NoError(err) + + require.Equal(resp.GetPrice(), fee) + }) +} diff --git a/x/dynamicfee/module.go b/x/dynamicfee/module.go new file mode 100644 index 000000000000..3d1301acb31b --- /dev/null +++ b/x/dynamicfee/module.go @@ -0,0 +1,194 @@ +package dynamicfee + +import ( + "context" + "encoding/json" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/spf13/cobra" + + "cosmossdk.io/core/appmodule" + "cosmossdk.io/depinject" + "cosmossdk.io/depinject/appconfig" + store "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/client/cli" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/keeper" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" + modulev1 "github.com/cosmos/cosmos-sdk/x/dynamicfee/types/module" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +// ConsensusVersion is the x/dynamicfee module's consensus version identifier. +const ConsensusVersion = 1 + +var ( + _ module.AppModuleBasic = AppModule{} + _ module.HasGenesis = AppModule{} + _ module.HasServices = AppModule{} + + _ appmodule.AppModule = AppModule{} + _ appmodule.HasEndBlocker = AppModule{} +) + +// AppModuleBasic defines the base interface that the x/dynamicfee module exposes to the application. +type AppModuleBasic struct { + cdc codec.Codec +} + +// Name returns the name of x/dynamicfee module. +func (amb AppModuleBasic) Name() string { return types.ModuleName } + +// RegisterLegacyAminoCodec registers the necessary types from the x/dynamicfee module for amino +// serialization. +func (amb AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers the necessary implementations / interfaces in the x/dynamicfee +// module w/ the interface-registry. +func (amb AppModuleBasic) RegisterInterfaces(ir codectypes.InterfaceRegistry) { + types.RegisterInterfaces(ir) +} + +// RegisterGRPCGatewayRoutes registers the necessary REST routes for the GRPC-gateway to +// the x/dynamicfee module QueryService on mux. This method panics on failure. +func (amb AppModuleBasic) RegisterGRPCGatewayRoutes(cliCtx client.Context, mux *runtime.ServeMux) { + // Register the gate-way routes w/ the provided mux. + if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(cliCtx)); err != nil { + panic(err) + } +} + +// GetTxCmd is a no-op, as no txs are registered for submission (apart from messages that +// can only be executed by governance). +func (amb AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd returns the x/dynamicfee module base query cli-command. +func (amb AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// AppModule represents an application module for the x/dynamicfee module. +type AppModule struct { + AppModuleBasic + + k keeper.Keeper +} + +// NewAppModule returns an application module for the x/dynamicfee module. +func NewAppModule(cdc codec.Codec, k keeper.Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{ + cdc: cdc, + }, + k: k, + } +} + +// EndBlock returns an endblocker for the x/feemarket module. +func (am AppModule) EndBlock(ctx context.Context) error { + return am.k.EndBlock(ctx) +} + +// IsAppModule implements the appmodule.AppModule interface. +func (am AppModule) IsAppModule() {} + +// IsOnePerModuleType implements the depinject.OnePerModuleType interface. +func (am AppModule) IsOnePerModuleType() {} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +// RegisterServices registers the module's services with the app's module configurator. +func (am AppModule) RegisterServices(cfc module.Configurator) { + types.RegisterMsgServer(cfc.MsgServer(), keeper.NewMsgServer(&am.k)) + types.RegisterQueryServer(cfc.QueryServer(), keeper.NewQueryServer(am.k)) +} + +// DefaultGenesis returns default genesis state as raw bytes for the dynamicfee +// module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultAIMDGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the dynamicfee module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { + var gs types.GenesisState + if err := cdc.UnmarshalJSON(bz, &gs); err != nil { + return err + } + + return gs.ValidateBasic() +} + +// InitGenesis performs the genesis initialization for the x/dynamicfee module. This method returns +// no validator set updates. This method panics on any errors. +func (am AppModule) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, bz json.RawMessage) { + var gs types.GenesisState + am.cdc.MustUnmarshalJSON(bz, &gs) + am.k.InitGenesis(ctx, gs) +} + +// ExportGenesis returns the dynamicfee module's exported genesis state as raw +// JSON bytes. This method panics on any error. +func (am AppModule) ExportGenesis(ctx sdk.Context, _ codec.JSONCodec) json.RawMessage { + gs := am.k.ExportGenesis(ctx) + return am.cdc.MustMarshalJSON(gs) +} + +func init() { + appconfig.RegisterModule( + &modulev1.Module{}, + appconfig.Provide(ProvideModule), + ) +} + +type Inputs struct { + depinject.In + + Config *modulev1.Module + Cdc codec.Codec + Key *store.KVStoreKey +} + +type Outputs struct { + depinject.Out + + Keeper keeper.Keeper + Module appmodule.AppModule +} + +func ProvideModule(in Inputs) Outputs { + var ( + authority sdk.AccAddress + err error + ) + if in.Config.Authority != "" { + authority, err = sdk.AccAddressFromBech32(in.Config.Authority) + if err != nil { + panic(err) + } + } else { + authority = authtypes.NewModuleAddress(govtypes.ModuleName) + } + + Keeper := keeper.NewKeeper( + in.Cdc, + in.Key, + nil, + authority.String(), + ) + + m := NewAppModule(in.Cdc, *Keeper) + + return Outputs{Keeper: *Keeper, Module: m} +} diff --git a/x/dynamicfee/module_simulation.go b/x/dynamicfee/module_simulation.go new file mode 100644 index 000000000000..1c9f280e5df0 --- /dev/null +++ b/x/dynamicfee/module_simulation.go @@ -0,0 +1,32 @@ +package dynamicfee + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/types/module" + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/simulation" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +// GenerateGenesisState returns a disabled dynamicfee module because the module +// does not work well with simulations. Especially the dynamicfee ante handler +// does not accept 0 fee coins which is quite common during simulation's +// operations. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + params := types.DefaultParams() + params.Enabled = false + genesis := types.NewGenesisState(params, types.DefaultState()) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) + fmt.Println("Dynamicfee module is disabled") +} + +// RegisterStoreDecoder registers a decoder. +func (am AppModule) RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns the all the module operations with their respective weights. +func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation { + return nil +} diff --git a/x/dynamicfee/post/expected_keepers.go b/x/dynamicfee/post/expected_keepers.go new file mode 100644 index 000000000000..1f9b5ef52e54 --- /dev/null +++ b/x/dynamicfee/post/expected_keepers.go @@ -0,0 +1,15 @@ +package post + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +type DynamicfeeKeeper interface { + GetState(ctx context.Context) (types.State, error) + GetParams(ctx context.Context) (types.Params, error) + GetMaxBlockGas(ctx context.Context, params types.Params) uint64 + SetState(ctx context.Context, state types.State) error + GetEnabledHeight(ctx context.Context) (int64, error) +} diff --git a/x/dynamicfee/post/expected_keepers_mocks_test.go b/x/dynamicfee/post/expected_keepers_mocks_test.go new file mode 100644 index 000000000000..46c078d105ff --- /dev/null +++ b/x/dynamicfee/post/expected_keepers_mocks_test.go @@ -0,0 +1,109 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: x/dynamicfee/post/expected_keepers.go + +// Package post_test is a generated GoMock package. +package post_test + +import ( + context "context" + reflect "reflect" + + types "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" + gomock "github.com/golang/mock/gomock" +) + +// MockDynamicfeeKeeper is a mock of DynamicfeeKeeper interface. +type MockDynamicfeeKeeper struct { + ctrl *gomock.Controller + recorder *MockDynamicfeeKeeperMockRecorder +} + +// MockDynamicfeeKeeperMockRecorder is the mock recorder for MockDynamicfeeKeeper. +type MockDynamicfeeKeeperMockRecorder struct { + mock *MockDynamicfeeKeeper +} + +// NewMockDynamicfeeKeeper creates a new mock instance. +func NewMockDynamicfeeKeeper(ctrl *gomock.Controller) *MockDynamicfeeKeeper { + mock := &MockDynamicfeeKeeper{ctrl: ctrl} + mock.recorder = &MockDynamicfeeKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDynamicfeeKeeper) EXPECT() *MockDynamicfeeKeeperMockRecorder { + return m.recorder +} + +// GetEnabledHeight mocks base method. +func (m *MockDynamicfeeKeeper) GetEnabledHeight(ctx context.Context) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEnabledHeight", ctx) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEnabledHeight indicates an expected call of GetEnabledHeight. +func (mr *MockDynamicfeeKeeperMockRecorder) GetEnabledHeight(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEnabledHeight", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).GetEnabledHeight), ctx) +} + +// GetMaxBlockGas mocks base method. +func (m *MockDynamicfeeKeeper) GetMaxBlockGas(ctx context.Context, params types.Params) uint64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMaxBlockGas", ctx, params) + ret0, _ := ret[0].(uint64) + return ret0 +} + +// GetMaxBlockGas indicates an expected call of GetMaxBlockGas. +func (mr *MockDynamicfeeKeeperMockRecorder) GetMaxBlockGas(ctx, params interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMaxBlockGas", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).GetMaxBlockGas), ctx, params) +} + +// GetParams mocks base method. +func (m *MockDynamicfeeKeeper) GetParams(ctx context.Context) (types.Params, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetParams", ctx) + ret0, _ := ret[0].(types.Params) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetParams indicates an expected call of GetParams. +func (mr *MockDynamicfeeKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetParams", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).GetParams), ctx) +} + +// GetState mocks base method. +func (m *MockDynamicfeeKeeper) GetState(ctx context.Context) (types.State, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetState", ctx) + ret0, _ := ret[0].(types.State) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetState indicates an expected call of GetState. +func (mr *MockDynamicfeeKeeperMockRecorder) GetState(ctx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetState", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).GetState), ctx) +} + +// SetState mocks base method. +func (m *MockDynamicfeeKeeper) SetState(ctx context.Context, state types.State) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetState", ctx, state) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetState indicates an expected call of SetState. +func (mr *MockDynamicfeeKeeperMockRecorder) SetState(ctx, state interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetState", reflect.TypeOf((*MockDynamicfeeKeeper)(nil).SetState), ctx, state) +} diff --git a/x/dynamicfee/post/post.go b/x/dynamicfee/post/post.go new file mode 100644 index 000000000000..acb9bc8523ec --- /dev/null +++ b/x/dynamicfee/post/post.go @@ -0,0 +1,78 @@ +package post + +import ( + errorsmod "cosmossdk.io/errors" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// DynamicfeeStateUpdateDecorator updates the state of the dynamic fee pricing +// based on the gas consumed in the gasmeter. Call next PostHandler if fees +// successfully deducted. +// CONTRACT: Tx must implement FeeTx interface +type DynamicfeeStateUpdateDecorator struct { + dynamicfeeKeeper DynamicfeeKeeper +} + +func NewDynamicfeeStateUpdateDecorator(fmk DynamicfeeKeeper) DynamicfeeStateUpdateDecorator { + return DynamicfeeStateUpdateDecorator{ + dynamicfeeKeeper: fmk, + } +} + +// PostHandle deducts the fee from the fee payer based on the min base fee and the gas consumed in the gasmeter. +// If there is a difference between the provided fee and the min-base fee, the difference is paid as a tip. +// Fees are sent to the x/dynamicfee fee-collector address. +func (dfd DynamicfeeStateUpdateDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate, success bool, next sdk.PostHandler) (sdk.Context, error) { + // GenTx consume no fee + if ctx.BlockHeight() == 0 { + return next(ctx, tx, simulate, success) + } + + // update dynamic fee pricing params + params, err := dfd.dynamicfeeKeeper.GetParams(ctx) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to get dynamicfee params") + } + + // return if disabled + if !params.Enabled { + return next(ctx, tx, simulate, success) + } + + enabledHeight, err := dfd.dynamicfeeKeeper.GetEnabledHeight(ctx) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to get dynamicfee enabled height") + } + + // if the current height is that which enabled the dynamicfee or lower, skip deduction + if ctx.BlockHeight() <= enabledHeight { + return next(ctx, tx, simulate, success) + } + + // update dynamic fee pricing state + state, err := dfd.dynamicfeeKeeper.GetState(ctx) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to get dynamicfee state") + } + + gas := ctx.GasMeter().GasConsumed() // use context gas consumed + + ctx.Logger().Info("dynamicfee post handle", + "gas consumed", gas, + ) + + maxBlockGas := dfd.dynamicfeeKeeper.GetMaxBlockGas(ctx, params) + + err = state.Update(gas, maxBlockGas) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to update dynamicfee state") + } + + err = dfd.dynamicfeeKeeper.SetState(ctx, state) + if err != nil { + return ctx, errorsmod.Wrapf(err, "unable to set dynamicfee state") + } + + return next(ctx, tx, simulate, success) +} diff --git a/x/dynamicfee/post/post_test.go b/x/dynamicfee/post/post_test.go new file mode 100644 index 000000000000..6a86f95c3669 --- /dev/null +++ b/x/dynamicfee/post/post_test.go @@ -0,0 +1,129 @@ +package post_test + +import ( + "testing" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + storetypes "cosmossdk.io/store/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/post" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +type mocks struct { + ctx sdk.Context + DynamicfeeKeeper *MockDynamicfeeKeeper +} + +func setupMocks(t *testing.T) mocks { + t.Helper() + ctrl := gomock.NewController(t) + return mocks{ + ctx: sdk.NewContext(nil, tmproto.Header{}, false, log.NewTestLogger(t)), + DynamicfeeKeeper: NewMockDynamicfeeKeeper(ctrl), + } +} + +func TestPostHandle(t *testing.T) { + tests := []struct { + name string + genTx bool + simulate bool + disableDynamicfee bool + setup func(mocks) + }{ + { + name: "ok: skip gentx", + genTx: true, + }, + { + name: "ok: dynamicfee disabled", + disableDynamicfee: true, + setup: func(m mocks) { + params := types.DefaultParams() + params.Enabled = false + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx).Return(params, nil) + }, + }, + { + name: "ok: enabled height not reached", + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil) + m.DynamicfeeKeeper.EXPECT().GetEnabledHeight(m.ctx).Return(int64(2), nil) + }, + }, + { + name: "ok: state updated", + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil) + m.DynamicfeeKeeper.EXPECT().GetEnabledHeight(m.ctx).Return(int64(0), nil) + m.DynamicfeeKeeper.EXPECT().GetState(m.ctx). + Return(types.DefaultState(), nil) + + gasConsumed := storetypes.Gas(1000) + m.ctx.GasMeter().ConsumeGas(gasConsumed, "") + + expectedState := types.DefaultState() + expectedState.Window[0] = gasConsumed + m.DynamicfeeKeeper.EXPECT().SetState(m.ctx, expectedState) + maxBlockGas := testutil.MaxBlockGas + m.DynamicfeeKeeper.EXPECT().GetMaxBlockGas(m.ctx, types.DefaultParams()).Return(uint64(maxBlockGas)) + }, + }, + { + name: "ok: simulate && state updated", + simulate: true, + setup: func(m mocks) { + m.DynamicfeeKeeper.EXPECT().GetParams(m.ctx). + Return(types.DefaultParams(), nil) + m.DynamicfeeKeeper.EXPECT().GetEnabledHeight(m.ctx).Return(int64(0), nil) + m.DynamicfeeKeeper.EXPECT().GetState(m.ctx). + Return(types.DefaultState(), nil) + + gasConsumed := storetypes.Gas(1000) + m.ctx.GasMeter().ConsumeGas(gasConsumed, "") + + expectedState := types.DefaultState() + expectedState.Window[0] = gasConsumed + m.DynamicfeeKeeper.EXPECT().SetState(m.ctx, expectedState) + maxBlockGas := testutil.MaxBlockGas + m.DynamicfeeKeeper.EXPECT().GetMaxBlockGas(m.ctx, types.DefaultParams()).Return(uint64(maxBlockGas)) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + m = setupMocks(t) + dfd = post.NewDynamicfeeStateUpdateDecorator(m.DynamicfeeKeeper) + nextInvoked bool + next = func(ctx sdk.Context, tx sdk.Tx, simulate, success bool) (sdk.Context, error) { + nextInvoked = true + return ctx, nil + } + ) + if tt.genTx { + m.ctx = m.ctx.WithBlockHeight(0) + } else { + m.ctx = m.ctx.WithBlockHeight(1) + } + if tt.setup != nil { + tt.setup(m) + } + + _, err := dfd.PostHandle(m.ctx, nil, tt.simulate, true, next) + + require.NoError(t, err) + assert.True(t, nextInvoked, "next is not invoked") + }) + } +} diff --git a/x/dynamicfee/simulation/decoder.go b/x/dynamicfee/simulation/decoder.go new file mode 100644 index 000000000000..0d96fe4e1aed --- /dev/null +++ b/x/dynamicfee/simulation/decoder.go @@ -0,0 +1,16 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/kv" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding dynamicfee type. +func NewDecodeStore(_ codec.Codec) func(kvA, kvB kv.Pair) string { + return func(kvA, _ kv.Pair) string { + panic(fmt.Sprintf("invalid dynamicfee key prefix %X", kvA.Key[:1])) + } +} diff --git a/x/dynamicfee/testutil/keeper.go b/x/dynamicfee/testutil/keeper.go new file mode 100644 index 000000000000..33b0ff5ad034 --- /dev/null +++ b/x/dynamicfee/testutil/keeper.go @@ -0,0 +1,51 @@ +package testutil + +import ( + "testing" + + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + tmtime "github.com/cometbft/cometbft/types/time" + + storetypes "cosmossdk.io/store/types" + + "github.com/cosmos/cosmos-sdk/testutil" + sdk "github.com/cosmos/cosmos-sdk/types" + moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/keeper" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" +) + +func SetupMsgServer(t *testing.T, maxBlockGas uint64) (types.MsgServer, *keeper.Keeper, sdk.Context) { + t.Helper() + k, ctx := SetupKeeper(t, maxBlockGas) + return keeper.NewMsgServer(k), k, ctx +} + +func SetupQueryServer(t *testing.T, maxBlockGas uint64) (types.QueryServer, *keeper.Keeper, sdk.Context) { + t.Helper() + k, ctx := SetupKeeper(t, maxBlockGas) + return keeper.NewQueryServer(*k), k, ctx +} + +const MaxBlockGas = 30_000_000 + +func SetupKeeper(t *testing.T, maxBlockGas uint64) (*keeper.Keeper, sdk.Context) { + t.Helper() + + key := storetypes.NewKVStoreKey(types.StoreKey) + testCtx := testutil.DefaultContextWithDB(t, key, storetypes.NewTransientStoreKey("transient_test")) + ctx := testCtx.Ctx.WithBlockHeader(tmproto.Header{Time: tmtime.Now()}) + // setup block max gas + if maxBlockGas == 0 { + maxBlockGas = MaxBlockGas + } + ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{Block: &tmproto.BlockParams{MaxGas: int64(maxBlockGas)}}) + encCfg := moduletestutil.MakeTestEncodingConfig() + types.RegisterInterfaces(encCfg.InterfaceRegistry) + // banktypes.RegisterInterfaces(encCfg.InterfaceRegistry) + authority := authtypes.NewModuleAddress(govtypes.ModuleName).String() + + return keeper.NewKeeper(encCfg.Codec, key, &types.ErrorDenomResolver{}, authority), ctx +} diff --git a/x/dynamicfee/types/codec.go b/x/dynamicfee/types/codec.go new file mode 100644 index 000000000000..24240048255a --- /dev/null +++ b/x/dynamicfee/types/codec.go @@ -0,0 +1,25 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/msgservice" +) + +// RegisterLegacyAminoCodec registers the necessary x/dynamicfee interfaces (messages) on the +// provided LegacyAmino codec. +func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + legacy.RegisterAminoMsg(cdc, &MsgUpdateParams{}, "atomone/x/dynamicfee/v1/MsgUpdateParams") +} + +// RegisterInterfaces registers the x/dynamicfee interfaces (messages + msg server) on the +// provided InterfaceRegistry. +func RegisterInterfaces(registry types.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgUpdateParams{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/x/dynamicfee/types/eip1559.go b/x/dynamicfee/types/eip1559.go new file mode 100644 index 000000000000..b5fa5994ca62 --- /dev/null +++ b/x/dynamicfee/types/eip1559.go @@ -0,0 +1,82 @@ +package types + +import ( + "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Note: We use the same default values as Ethereum for the EIP-1559 dynamic +// fee pricing implementation. These parameters do not implement the AIMD +// learning rate adjustment algorithm. + +var ( + // DefaultWindow is the default window size for the sliding window + // used to calculate the base fee. In the base EIP-1559 implementation, + // only the previous block is considered. + DefaultWindow uint64 = 1 + + // DefaultAlpha is not used in the base EIP-1559 implementation. + DefaultAlpha = math.LegacyMustNewDecFromStr("0.0") + + // DefaultBeta is not used in the base EIP-1559 implementation. + DefaultBeta = math.LegacyMustNewDecFromStr("1.0") + + // DefaultGamma is not used in the base EIP-1559 implementation. + DefaultGamma = math.LegacyMustNewDecFromStr("0.0") + + // DefaultMinBaseGasPrice is the default minimum base gas price. + DefaultMinBaseGasPrice = math.LegacyMustNewDecFromStr("0.01") + + // DefaultTargetBlockUtilization is the default target block utilization. + DefaultTargetBlockUtilization = math.LegacyMustNewDecFromStr("0.5") + + // DefaultMaxBlockGas is the default max block gas that is used + // when consensus_params.block.max_gas returns 0 or -1 + DefaultMaxBlockGas uint64 = 100_000_000 + + // DefaultMinLearningRate is not used in the base EIP-1559 implementation. + DefaultMinLearningRate = math.LegacyMustNewDecFromStr("0.125") + + // DefaultMaxLearningRate is not used in the base EIP-1559 implementation. + DefaultMaxLearningRate = math.LegacyMustNewDecFromStr("0.125") + + // DefaultFeeDenom is the Cosmos SDK default bond denom. + DefaultFeeDenom = sdk.DefaultBondDenom +) + +// DefaultParams returns a default set of parameters that implements +// the EIP-1559 dynamic fee pricing implementation without the AIMD +// learning rate adjustment algorithm. +func DefaultParams() Params { + return NewParams( + DefaultWindow, + DefaultAlpha, + DefaultBeta, + DefaultGamma, + DefaultMinBaseGasPrice, + DefaultTargetBlockUtilization, + DefaultMaxBlockGas, + DefaultMinLearningRate, + DefaultMaxLearningRate, + DefaultFeeDenom, + true, + ) +} + +// DefaultState returns the default state for the EIP-1559 dynamic fee pricing +// implementation without the AIMD learning rate adjustment algorithm. +func DefaultState() State { + return NewState( + DefaultWindow, + DefaultMinBaseGasPrice, + DefaultMinLearningRate, + ) +} + +// DefaultGenesisState returns a default genesis state that implements +// the EIP-1559 dynamic fee pricing implementation without the AIMD +// learning rate adjustment algorithm. +func DefaultGenesisState() *GenesisState { + return NewGenesisState(DefaultParams(), DefaultState()) +} diff --git a/x/dynamicfee/types/eip1559_aimd.go b/x/dynamicfee/types/eip1559_aimd.go new file mode 100644 index 000000000000..695e945894ad --- /dev/null +++ b/x/dynamicfee/types/eip1559_aimd.go @@ -0,0 +1,87 @@ +package types + +import "cosmossdk.io/math" + +// Note: The following constants are the default values for the AIMD EIP-1559 +// dynamic fee pricing implementation. This implements an adjustable learning +// rate algorithm that is not present in the base EIP-1559 implementation. + +var ( + // DefaultAIMDWindow is the default window size for the sliding window + // used to calculate the base fee. + DefaultAIMDWindow uint64 = 8 + + // DefaultAIMDAlpha is the default alpha value for the learning + // rate calculation. This value determines how much we want to additively + // increase the learning rate when the target block size is exceeded. + DefaultAIMDAlpha = math.LegacyMustNewDecFromStr("0.025") + + // DefaultAIMDBeta is the default beta value for the learning rate + // calculation. This value determines how much we want to multiplicatively + // decrease the learning rate when the target gas is not met. + DefaultAIMDBeta = math.LegacyMustNewDecFromStr("0.95") + + // DefaultAIMDGamma is the default threshold for determining whether + // to increase or decrease the learning rate. In this case, we increase + // the learning rate if the block gas within the window is greater + // than 0.75 or less than 0.25. Otherwise, we multiplicatively decrease + // the learning rate. + DefaultAIMDGamma = math.LegacyMustNewDecFromStr("0.25") + + // DefaultAIMDMinBaseGasPrice is the default minimum base gas price. + DefaultAIMDMinBaseGasPrice = DefaultMinBaseGasPrice + + // DefaultAIMDTargetBlockUtilization is the default target block utilization. + DefaultAIMDTargetBlockUtilization = math.LegacyMustNewDecFromStr("0.5") + + // DefaultMaxBlockGas is the default max block gas that is used + // when consensus_params.block.max_gas returns 0 or -1 + DefaultAIMDMaxBlockGas uint64 = 100_000_000 + + // DefaultAIMDMinLearningRate is the default minimum learning rate. + DefaultAIMDMinLearningRate = math.LegacyMustNewDecFromStr("0.01") + + // DefaultAIMDMaxLearningRate is the default maximum learning rate. + DefaultAIMDMaxLearningRate = math.LegacyMustNewDecFromStr("0.50") + + // DefaultAIMDFeeDenom is the Cosmos SDK default bond denom. + DefaultAIMDFeeDenom = DefaultFeeDenom +) + +// DefaultAIMDParams returns a default set of parameters that implements +// the AIMD EIP-1559 dynamic fee pricing implementation. These parameters +// allow for the learning rate to be dynamically adjusted based on the block +// gas within the window. +func DefaultAIMDParams() Params { + return NewParams( + DefaultAIMDWindow, + DefaultAIMDAlpha, + DefaultAIMDBeta, + DefaultAIMDGamma, + DefaultAIMDMinBaseGasPrice, + DefaultAIMDTargetBlockUtilization, + DefaultAIMDMaxBlockGas, + DefaultAIMDMinLearningRate, + DefaultAIMDMaxLearningRate, + DefaultAIMDFeeDenom, + true, + ) +} + +// DefaultAIMDState returns the default state for the AIMD EIP-1559 dynamic +// fee pricing implementation. This implementation uses a sliding window to +// track the block gas and dynamically adjusts the learning rate based on the +// gas within the window. +func DefaultAIMDState() State { + return NewState( + DefaultAIMDWindow, + DefaultAIMDMinBaseGasPrice, + DefaultAIMDMinLearningRate, + ) +} + +// DefaultAIMDGenesisState returns a default genesis state that implements +// the AIMD EIP-1559 dynamic fee pricing implementation. +func DefaultAIMDGenesisState() *GenesisState { + return NewGenesisState(DefaultAIMDParams(), DefaultAIMDState()) +} diff --git a/x/dynamicfee/types/errors.go b/x/dynamicfee/types/errors.go new file mode 100644 index 000000000000..8d1ede6a6c0e --- /dev/null +++ b/x/dynamicfee/types/errors.go @@ -0,0 +1,12 @@ +package types + +import ( + sdkerrors "cosmossdk.io/errors" +) + +var ( + ErrNoFeeCoins = sdkerrors.New(ModuleName, 1, "no fee coin provided. Must provide one.") + ErrTooManyFeeCoins = sdkerrors.New(ModuleName, 2, "too many fee coins provided. Only one fee coin may be provided") + ErrResolverNotSet = sdkerrors.New(ModuleName, 3, "denom resolver interface not set. Only the dynamicfee base fee denomination can be used") + ErrMaxGasExceeded = sdkerrors.New(ModuleName, 4, "block gas cannot exceed max block gas") +) diff --git a/x/dynamicfee/types/genesis.go b/x/dynamicfee/types/genesis.go new file mode 100644 index 000000000000..90e25cd9a1e6 --- /dev/null +++ b/x/dynamicfee/types/genesis.go @@ -0,0 +1,35 @@ +package types + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" +) + +// NewGenesisState returns a new genesis state for the module. +func NewGenesisState( + params Params, + state State, +) *GenesisState { + return &GenesisState{ + Params: params, + State: state, + } +} + +// ValidateBasic performs basic validation of the genesis state data returning an +// error for any failed validation criteria. +func (gs *GenesisState) ValidateBasic() error { + if err := gs.Params.ValidateBasic(); err != nil { + return err + } + return gs.State.ValidateBasic() +} + +// GetGenesisStateFromAppState returns x/dynamicfee GenesisState given raw application +// genesis state. +func GetGenesisStateFromAppState(cdc codec.Codec, appState map[string]json.RawMessage) GenesisState { + var gs GenesisState + cdc.MustUnmarshalJSON(appState[ModuleName], &gs) + return gs +} diff --git a/x/dynamicfee/types/genesis.pb.go b/x/dynamicfee/types/genesis.pb.go new file mode 100644 index 000000000000..d6309102df18 --- /dev/null +++ b/x/dynamicfee/types/genesis.pb.go @@ -0,0 +1,759 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: cosmos/dynamicfee/v1/genesis.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/gogoproto/gogoproto" + 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 dynamicfee module's genesis state. +type GenesisState struct { + // Params are the parameters for the dynamicfee module. These parameters + // can be utilized to implement both the base EIP-1559 dynamic fee pricing + // and the AIMD EIP-1559 dynamic fee pricing. + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + // State contains the current state of the AIMD dynamic fee pricer. + State State `protobuf:"bytes,2,opt,name=state,proto3" json:"state"` +} + +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_4037043791cf94ce, []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 (m *GenesisState) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +func (m *GenesisState) GetState() State { + if m != nil { + return m.State + } + return State{} +} + +// State is utilized to track the current state of the dynamic fee pricer. +// This includes the current base fee, learning rate, and block gas within the +// specified AIMD window. +type State struct { + // BaseGasPrice is the current base fee. This is denominated in the fee per + // gas unit. + BaseGasPrice cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=base_gas_price,json=baseGasPrice,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"base_gas_price"` + // LearningRate is the current learning rate. + LearningRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=learning_rate,json=learningRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"learning_rate"` + // Window contains a list of the last blocks' gas values. This is used + // to calculate the next base fee. This stores the number of units of gas + // consumed per block. + Window []uint64 `protobuf:"varint,3,rep,packed,name=window,proto3" json:"window,omitempty"` + // Index is the index of the current block in the block gas window. + Index uint64 `protobuf:"varint,4,opt,name=index,proto3" json:"index,omitempty"` +} + +func (m *State) Reset() { *m = State{} } +func (m *State) String() string { return proto.CompactTextString(m) } +func (*State) ProtoMessage() {} +func (*State) Descriptor() ([]byte, []int) { + return fileDescriptor_4037043791cf94ce, []int{1} +} +func (m *State) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *State) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_State.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 *State) XXX_Merge(src proto.Message) { + xxx_messageInfo_State.Merge(m, src) +} +func (m *State) XXX_Size() int { + return m.Size() +} +func (m *State) XXX_DiscardUnknown() { + xxx_messageInfo_State.DiscardUnknown(m) +} + +var xxx_messageInfo_State proto.InternalMessageInfo + +func (m *State) GetWindow() []uint64 { + if m != nil { + return m.Window + } + return nil +} + +func (m *State) GetIndex() uint64 { + if m != nil { + return m.Index + } + return 0 +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "cosmos.dynamicfee.v1.GenesisState") + proto.RegisterType((*State)(nil), "cosmos.dynamicfee.v1.State") +} + +func init() { + proto.RegisterFile("cosmos/dynamicfee/v1/genesis.proto", fileDescriptor_4037043791cf94ce) +} + +var fileDescriptor_4037043791cf94ce = []byte{ + // 361 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x91, 0x4d, 0x4b, 0x02, 0x41, + 0x18, 0xc7, 0x77, 0x72, 0x15, 0x9a, 0xac, 0xc3, 0x22, 0xb1, 0x69, 0xac, 0xe6, 0xc9, 0x8b, 0xb3, + 0x58, 0x87, 0xa0, 0xa3, 0x08, 0x12, 0x74, 0x90, 0x0d, 0x0a, 0xba, 0xc8, 0x38, 0x3b, 0x8d, 0x83, + 0xed, 0x8e, 0xec, 0x6c, 0xbe, 0x9c, 0xfb, 0x02, 0x7d, 0x98, 0x3e, 0x84, 0x47, 0xe9, 0x14, 0x1d, + 0x24, 0x14, 0xfa, 0x1c, 0x31, 0x3b, 0x23, 0x75, 0xf0, 0xd4, 0x69, 0xf7, 0xe1, 0xf9, 0xfd, 0x5f, + 0x86, 0x07, 0xd6, 0x89, 0x90, 0x91, 0x90, 0x7e, 0x38, 0x8f, 0x71, 0xc4, 0xc9, 0x23, 0xa5, 0xfe, + 0xa4, 0xe5, 0x33, 0x1a, 0x53, 0xc9, 0x25, 0x1a, 0x27, 0x22, 0x15, 0x4e, 0x49, 0x33, 0xe8, 0x97, + 0x41, 0x93, 0x56, 0xb9, 0xc4, 0x04, 0x13, 0x19, 0xe0, 0xab, 0x3f, 0xcd, 0x96, 0x4f, 0x34, 0xdb, + 0xd7, 0x0b, 0x23, 0xd4, 0xab, 0xb3, 0x9d, 0x51, 0x63, 0x9c, 0xe0, 0xc8, 0x20, 0xf5, 0x17, 0x00, + 0x8b, 0x5d, 0x9d, 0x7d, 0x9b, 0xe2, 0x94, 0x3a, 0x57, 0xb0, 0xa0, 0x01, 0x17, 0xd4, 0x40, 0xe3, + 0xe0, 0xfc, 0x14, 0xed, 0xea, 0x82, 0x7a, 0x19, 0xd3, 0xb6, 0x17, 0xab, 0xaa, 0x15, 0x18, 0x85, + 0x73, 0x09, 0xf3, 0x52, 0x99, 0xb8, 0x7b, 0x99, 0xb4, 0xb2, 0x5b, 0x9a, 0xe5, 0x18, 0xa5, 0xe6, + 0xeb, 0xdf, 0x00, 0xe6, 0x75, 0xfc, 0x3d, 0x3c, 0x1a, 0x60, 0x49, 0xfb, 0x0c, 0xab, 0x17, 0x71, + 0x42, 0xb3, 0x1a, 0xfb, 0xed, 0x96, 0xc2, 0x3f, 0x57, 0xd5, 0x8a, 0xb6, 0x94, 0xe1, 0x08, 0x71, + 0xe1, 0x47, 0x38, 0x1d, 0xa2, 0x1b, 0xca, 0x30, 0x99, 0x77, 0x28, 0x79, 0x7f, 0x6b, 0x42, 0x93, + 0xd8, 0xa1, 0x24, 0x28, 0x2a, 0xa3, 0x2e, 0x96, 0x3d, 0x65, 0xe3, 0xdc, 0xc1, 0xc3, 0x27, 0x8a, + 0x93, 0x98, 0xc7, 0xac, 0x9f, 0x6c, 0x3b, 0xfe, 0xcf, 0x77, 0xeb, 0x13, 0xa8, 0xc2, 0xc7, 0xb0, + 0x30, 0xe5, 0x71, 0x28, 0xa6, 0x6e, 0xae, 0x96, 0x6b, 0xd8, 0x81, 0x99, 0x9c, 0x12, 0xcc, 0xf3, + 0x38, 0xa4, 0x33, 0xd7, 0xae, 0x81, 0x86, 0x1d, 0xe8, 0xa1, 0x7d, 0xbd, 0x58, 0x7b, 0x60, 0xb9, + 0xf6, 0xc0, 0xd7, 0xda, 0x03, 0xaf, 0x1b, 0xcf, 0x5a, 0x6e, 0x3c, 0xeb, 0x63, 0xe3, 0x59, 0x0f, + 0x3e, 0xe3, 0xe9, 0xf0, 0x79, 0x80, 0x88, 0x88, 0xcc, 0x11, 0xcd, 0xa7, 0x29, 0xc3, 0x91, 0x3f, + 0xfb, 0x7b, 0xc3, 0x74, 0x3e, 0xa6, 0x72, 0x50, 0xc8, 0x0e, 0x78, 0xf1, 0x13, 0x00, 0x00, 0xff, + 0xff, 0x41, 0xb2, 0x3f, 0xd6, 0x50, 0x02, 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 + { + size, err := m.State.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *State) 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 *State) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *State) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Index != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.Index)) + i-- + dAtA[i] = 0x20 + } + if len(m.Window) > 0 { + dAtA4 := make([]byte, len(m.Window)*10) + var j3 int + for _, num := range m.Window { + for num >= 1<<7 { + dAtA4[j3] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j3++ + } + dAtA4[j3] = uint8(num) + j3++ + } + i -= j3 + copy(dAtA[i:], dAtA4[:j3]) + i = encodeVarintGenesis(dAtA, i, uint64(j3)) + i-- + dAtA[i] = 0x1a + } + { + size := m.LearningRate.Size() + i -= size + if _, err := m.LearningRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.BaseGasPrice.Size() + i -= size + if _, err := m.BaseGasPrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + 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 + l = m.Params.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.State.Size() + n += 1 + l + sovGenesis(uint64(l)) + return n +} + +func (m *State) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.BaseGasPrice.Size() + n += 1 + l + sovGenesis(uint64(l)) + l = m.LearningRate.Size() + n += 1 + l + sovGenesis(uint64(l)) + if len(m.Window) > 0 { + l = 0 + for _, e := range m.Window { + l += sovGenesis(uint64(e)) + } + n += 1 + sovGenesis(uint64(l)) + l + } + if m.Index != 0 { + n += 1 + sovGenesis(uint64(m.Index)) + } + 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 { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.State.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + 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 (m *State) 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: State: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: State: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BaseGasPrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.BaseGasPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LearningRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.LearningRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Window = append(m.Window, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } + } + elementCount = count + if elementCount != 0 && len(m.Window) == 0 { + m.Window = make([]uint64, 0, elementCount) + } + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Window = append(m.Window, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Window", wireType) + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + m.Index = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Index |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + 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/dynamicfee/types/genesis_test.go b/x/dynamicfee/types/genesis_test.go new file mode 100644 index 000000000000..a0c1c3457aa4 --- /dev/null +++ b/x/dynamicfee/types/genesis_test.go @@ -0,0 +1,21 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestGenesis(t *testing.T) { + t.Run("can create a new default genesis state", func(t *testing.T) { + gs := types.DefaultGenesisState() + require.NoError(t, gs.ValidateBasic()) + }) + + t.Run("can accept a valid genesis state for AIMD eip-1559", func(t *testing.T) { + gs := types.DefaultAIMDGenesisState() + require.NoError(t, gs.ValidateBasic()) + }) +} diff --git a/x/dynamicfee/types/keys.go b/x/dynamicfee/types/keys.go new file mode 100644 index 000000000000..9a36bc8855d6 --- /dev/null +++ b/x/dynamicfee/types/keys.go @@ -0,0 +1,25 @@ +package types + +const ( + // ModuleName is the name of the dynamicfee module. + ModuleName = "dynamicfee" + // StoreKey is the store key string for the dynamicfee module. + StoreKey = ModuleName +) + +const ( + prefixParams = iota + 1 + prefixState + prefixEnableHeight = 3 +) + +var ( + // KeyParams is the store key for the dynamicfee module's parameters. + KeyParams = []byte{prefixParams} + + // KeyState is the store key for the dynamicfee module's data. + KeyState = []byte{prefixState} + + // KeyEnabledHeight is the store key for the dynamicfee module's enabled height. + KeyEnabledHeight = []byte{prefixEnableHeight} +) diff --git a/x/dynamicfee/types/module/module.pb.go b/x/dynamicfee/types/module/module.pb.go new file mode 100644 index 000000000000..f554aec0e97c --- /dev/null +++ b/x/dynamicfee/types/module/module.pb.go @@ -0,0 +1,323 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: cosmos/dynamicfee/module/v1/module.proto + +package module + +import ( + _ "cosmossdk.io/api/cosmos/app/v1alpha1" + 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 + +// Module is the config object of the builder module. +type Module struct { + // Authority defines the custom module authority. If not set, defaults to the + // governance module. + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` +} + +func (m *Module) Reset() { *m = Module{} } +func (m *Module) String() string { return proto.CompactTextString(m) } +func (*Module) ProtoMessage() {} +func (*Module) Descriptor() ([]byte, []int) { + return fileDescriptor_db3fe058d3335c08, []int{0} +} +func (m *Module) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Module) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Module.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 *Module) XXX_Merge(src proto.Message) { + xxx_messageInfo_Module.Merge(m, src) +} +func (m *Module) XXX_Size() int { + return m.Size() +} +func (m *Module) XXX_DiscardUnknown() { + xxx_messageInfo_Module.DiscardUnknown(m) +} + +var xxx_messageInfo_Module proto.InternalMessageInfo + +func (m *Module) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func init() { + proto.RegisterType((*Module)(nil), "cosmos.dynamicfee.module.v1.Module") +} + +func init() { + proto.RegisterFile("cosmos/dynamicfee/module/v1/module.proto", fileDescriptor_db3fe058d3335c08) +} + +var fileDescriptor_db3fe058d3335c08 = []byte{ + // 198 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x48, 0xce, 0x2f, 0xce, + 0xcd, 0x2f, 0xd6, 0x4f, 0xa9, 0xcc, 0x4b, 0xcc, 0xcd, 0x4c, 0x4e, 0x4b, 0x4d, 0xd5, 0xcf, 0xcd, + 0x4f, 0x29, 0xcd, 0x49, 0xd5, 0x2f, 0x33, 0x84, 0xb2, 0xf4, 0x0a, 0x8a, 0xf2, 0x4b, 0xf2, 0x85, + 0xa4, 0x21, 0x2a, 0xf5, 0x10, 0x2a, 0xf5, 0xa0, 0xf2, 0x65, 0x86, 0x52, 0x0a, 0x50, 0x63, 0x12, + 0x0b, 0x0a, 0xf4, 0xcb, 0x0c, 0x13, 0x73, 0x0a, 0x32, 0x12, 0x51, 0xb5, 0x2b, 0x45, 0x72, 0xb1, + 0xf9, 0x82, 0xf9, 0x42, 0x32, 0x5c, 0x9c, 0x89, 0xa5, 0x25, 0x19, 0xf9, 0x45, 0x99, 0x25, 0x95, + 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x08, 0x01, 0x2b, 0xc3, 0x5d, 0x07, 0xa6, 0xdd, 0x62, + 0xd4, 0xe6, 0xd2, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x87, 0x1a, + 0x0e, 0xa1, 0x74, 0x8b, 0x53, 0xb2, 0xf5, 0x2b, 0x90, 0x1c, 0xec, 0x14, 0x70, 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, 0x66, 0x44, 0x1b, 0xa2, 0x5f, 0x52, 0x59, 0x90, 0x5a, + 0x0c, 0x75, 0x72, 0x12, 0x1b, 0xd8, 0xcd, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x66, 0xf0, + 0x81, 0x7d, 0x1e, 0x01, 0x00, 0x00, +} + +func (m *Module) 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 *Module) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Module) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintModule(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintModule(dAtA []byte, offset int, v uint64) int { + offset -= sovModule(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Module) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovModule(uint64(l)) + } + return n +} + +func sovModule(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozModule(x uint64) (n int) { + return sovModule(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Module) 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 ErrIntOverflowModule + } + 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: Module: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Module: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowModule + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthModule + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthModule + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipModule(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthModule + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipModule(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, ErrIntOverflowModule + } + 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, ErrIntOverflowModule + } + 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, ErrIntOverflowModule + } + 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, ErrInvalidLengthModule + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupModule + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthModule + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthModule = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowModule = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupModule = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/dynamicfee/types/msgs.go b/x/dynamicfee/types/msgs.go new file mode 100644 index 000000000000..eaa2a8fe9ac3 --- /dev/null +++ b/x/dynamicfee/types/msgs.go @@ -0,0 +1,33 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var _ sdk.Msg = &MsgUpdateParams{} + +// NewMsgUpdateParams returns a new message to update the x/dynamicfee module's parameters. +func NewMsgUpdateParams(authority string, params Params) MsgUpdateParams { + return MsgUpdateParams{ + Authority: authority, + Params: params, + } +} + +// GetSigners implements GetSigners for the msg. +func (m *MsgUpdateParams) GetSigners() []sdk.AccAddress { + addr, _ := sdk.AccAddressFromBech32(m.Authority) + return []sdk.AccAddress{addr} +} + +// ValidateBasic determines whether the information in the message is formatted correctly, specifically +// whether the authority is a valid acc-address. +func (m *MsgUpdateParams) ValidateBasic() error { + // validate authority address + _, err := sdk.AccAddressFromBech32(m.Authority) + if err != nil { + return err + } + + return m.Params.ValidateBasic() +} diff --git a/x/dynamicfee/types/msgs_test.go b/x/dynamicfee/types/msgs_test.go new file mode 100644 index 000000000000..f834e7920cdd --- /dev/null +++ b/x/dynamicfee/types/msgs_test.go @@ -0,0 +1,31 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestMsgUpdateParams(t *testing.T) { + t.Run("should reject a message with an invalid authority address", func(t *testing.T) { + msg := types.NewMsgUpdateParams("invalid", types.DefaultParams()) + err := msg.ValidateBasic() + require.Error(t, err) + }) + + t.Run("should accept an empty message with a valid authority address", func(t *testing.T) { + msg := types.NewMsgUpdateParams(sdk.AccAddress("test").String(), types.DefaultParams()) + err := msg.ValidateBasic() + require.NoError(t, err) + }) + + t.Run("should reject a message with invalid params", func(t *testing.T) { + msg := types.NewMsgUpdateParams(sdk.AccAddress("test").String(), types.DefaultParams()) + msg.Params.Window = 0 + err := msg.ValidateBasic() + require.Error(t, err) + }) +} diff --git a/x/dynamicfee/types/params.go b/x/dynamicfee/types/params.go new file mode 100644 index 000000000000..d7b0d0814fd0 --- /dev/null +++ b/x/dynamicfee/types/params.go @@ -0,0 +1,87 @@ +package types + +import ( + fmt "fmt" + + "cosmossdk.io/math" +) + +// NewParams instantiates a new EIP-1559 Params object. This params object is +// utilized to implement both the base EIP-1559 fee and AIMD EIP-1559 dynamic +// fee pricing implementations. +func NewParams( + window uint64, + alpha math.LegacyDec, + beta math.LegacyDec, + gamma math.LegacyDec, + minBaseGasPrice math.LegacyDec, + targetBlockUtilization math.LegacyDec, + defaultMaxBlockGas uint64, + minLearingRate math.LegacyDec, + maxLearningRate math.LegacyDec, + feeDenom string, + enabled bool, +) Params { + return Params{ + Alpha: alpha, + Beta: beta, + Gamma: gamma, + MinBaseGasPrice: minBaseGasPrice, + TargetBlockUtilization: targetBlockUtilization, + DefaultMaxBlockGas: defaultMaxBlockGas, + MinLearningRate: minLearingRate, + MaxLearningRate: maxLearningRate, + Window: window, + FeeDenom: feeDenom, + Enabled: enabled, + } +} + +// ValidateBasic performs basic validation on the parameters. +func (p *Params) ValidateBasic() error { + if p.Window == 0 { + return fmt.Errorf("window cannot be zero") + } + + if p.Alpha.IsNil() || p.Alpha.IsNegative() { + return fmt.Errorf("alpha cannot be nil must be between [0, inf)") + } + + if p.Beta.IsNil() || p.Beta.IsNegative() || p.Beta.GT(math.LegacyOneDec()) { + return fmt.Errorf("beta cannot be nil and must be between [0, 1]") + } + + if p.Gamma.IsNil() || p.Gamma.IsNegative() || p.Gamma.GT(math.LegacyMustNewDecFromStr("0.5")) { + return fmt.Errorf("theta cannot be nil and must be between [0, 0.5]") + } + + if p.MinBaseGasPrice.IsNil() || !p.MinBaseGasPrice.GTE(math.LegacyZeroDec()) { + return fmt.Errorf("min base gas price cannot be nil and must be greater than or equal to zero") + } + + if p.TargetBlockUtilization.IsNil() || !p.TargetBlockUtilization.IsPositive() || p.TargetBlockUtilization.GT(math.LegacyOneDec()) { + return fmt.Errorf("target block utilization must be between (0, 1]") + } + + if p.DefaultMaxBlockGas == 0 { + return fmt.Errorf("default max block gas must be greater than 0") + } + + if p.MinLearningRate.IsNil() || p.MinLearningRate.IsNegative() { + return fmt.Errorf("min learning rate cannot be negative or nil") + } + + if p.MaxLearningRate.IsNil() || p.MaxLearningRate.IsNegative() { + return fmt.Errorf("max learning rate cannot be negative or nil") + } + + if p.MinLearningRate.GT(p.MaxLearningRate) { + return fmt.Errorf("min learning rate cannot be greater than max learning rate") + } + + if p.FeeDenom == "" { + return fmt.Errorf("fee denom must be set") + } + + return nil +} diff --git a/x/dynamicfee/types/params.pb.go b/x/dynamicfee/types/params.pb.go new file mode 100644 index 000000000000..b351f124c438 --- /dev/null +++ b/x/dynamicfee/types/params.pb.go @@ -0,0 +1,806 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: cosmos/dynamicfee/v1/params.proto + +package types + +import ( + cosmossdk_io_math "cosmossdk.io/math" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/gogoproto/gogoproto" + 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 + +// Params contains the required set of parameters for the EIP1559 dynamic fee +// pricing implementation. +type Params struct { + // Alpha is the amount we additively increase the learning rate + // when it is above or below the target +/- threshold. + // + // Must be > 0. + Alpha cosmossdk_io_math.LegacyDec `protobuf:"bytes,1,opt,name=alpha,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"alpha"` + // Beta is the amount we multiplicatively decrease the learning rate + // when it is within the target +/- threshold. + // + // Must be [0, 1]. + Beta cosmossdk_io_math.LegacyDec `protobuf:"bytes,2,opt,name=beta,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"beta"` + // Gamma is the threshold for the learning rate. If the learning rate is + // above or below the target +/- threshold, we additively increase the + // learning rate by Alpha. Otherwise, we multiplicatively decrease the + // learning rate by Beta. + // + // Must be [0, 0.5]. + Gamma cosmossdk_io_math.LegacyDec `protobuf:"bytes,3,opt,name=gamma,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"gamma"` + // MinBaseGasPrice determines the initial gas price of the module and the + // global minimum for the network. + MinBaseGasPrice cosmossdk_io_math.LegacyDec `protobuf:"bytes,5,opt,name=min_base_gas_price,json=minBaseGasPrice,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_base_gas_price"` + // TargetBlockUtilization is the target block utilization expressed as a + // decimal value between 0 and 1. It is the target percentage utilization + // of the block in relation to the consensus_params.block.max_gas parameter. + TargetBlockUtilization cosmossdk_io_math.LegacyDec `protobuf:"bytes,6,opt,name=target_block_utilization,json=targetBlockUtilization,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"target_block_utilization"` + // DefaultMaxBlockGas is the default max block gas. + // This parameter is used by the dynamicfee module + // in the case consensus_params.block.max_gas returns 0 or -1. + DefaultMaxBlockGas uint64 `protobuf:"varint,7,opt,name=default_max_block_gas,json=defaultMaxBlockGas,proto3" json:"default_max_block_gas,omitempty"` + // MinLearningRate is the lower bound for the learning rate. + MinLearningRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,8,opt,name=min_learning_rate,json=minLearningRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"min_learning_rate"` + // MaxLearningRate is the upper bound for the learning rate. + MaxLearningRate cosmossdk_io_math.LegacyDec `protobuf:"bytes,9,opt,name=max_learning_rate,json=maxLearningRate,proto3,customtype=cosmossdk.io/math.LegacyDec" json:"max_learning_rate"` + // Window defines the window size for calculating an adaptive learning rate + // over a moving window of blocks. + Window uint64 `protobuf:"varint,10,opt,name=window,proto3" json:"window,omitempty"` + // FeeDenom is the denom that will be used for all fee payments. + FeeDenom string `protobuf:"bytes,11,opt,name=fee_denom,json=feeDenom,proto3" json:"fee_denom,omitempty"` + // Enabled is a boolean that determines whether the EIP1559 dynamic fee + // pricing is enabled. + Enabled bool `protobuf:"varint,12,opt,name=enabled,proto3" json:"enabled,omitempty"` +} + +func (m *Params) Reset() { *m = Params{} } +func (m *Params) String() string { return proto.CompactTextString(m) } +func (*Params) ProtoMessage() {} +func (*Params) Descriptor() ([]byte, []int) { + return fileDescriptor_6b60cb39f80fc701, []int{0} +} +func (m *Params) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Params) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Params.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 *Params) XXX_Merge(src proto.Message) { + xxx_messageInfo_Params.Merge(m, src) +} +func (m *Params) XXX_Size() int { + return m.Size() +} +func (m *Params) XXX_DiscardUnknown() { + xxx_messageInfo_Params.DiscardUnknown(m) +} + +var xxx_messageInfo_Params proto.InternalMessageInfo + +func (m *Params) GetDefaultMaxBlockGas() uint64 { + if m != nil { + return m.DefaultMaxBlockGas + } + return 0 +} + +func (m *Params) GetWindow() uint64 { + if m != nil { + return m.Window + } + return 0 +} + +func (m *Params) GetFeeDenom() string { + if m != nil { + return m.FeeDenom + } + return "" +} + +func (m *Params) GetEnabled() bool { + if m != nil { + return m.Enabled + } + return false +} + +func init() { + proto.RegisterType((*Params)(nil), "cosmos.dynamicfee.v1.Params") +} + +func init() { proto.RegisterFile("cosmos/dynamicfee/v1/params.proto", fileDescriptor_6b60cb39f80fc701) } + +var fileDescriptor_6b60cb39f80fc701 = []byte{ + // 446 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0x41, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0x1b, 0xdd, 0x76, 0xdb, 0x51, 0x10, 0x87, 0x75, 0x19, 0x77, 0x21, 0x5b, 0x3d, 0xf5, + 0xb2, 0x09, 0xc5, 0x6f, 0x50, 0x2a, 0x45, 0x58, 0x61, 0x09, 0x78, 0x11, 0x34, 0xbc, 0x24, 0xaf, + 0xd3, 0xa1, 0x99, 0x99, 0x90, 0x99, 0xee, 0xa6, 0x7e, 0x0a, 0x3f, 0x8c, 0x1f, 0x62, 0x8f, 0x8b, + 0x27, 0xf1, 0xb0, 0x48, 0x7b, 0xf7, 0x33, 0x48, 0x26, 0x91, 0x55, 0x8f, 0xf1, 0x94, 0xbc, 0x97, + 0xff, 0xff, 0xf7, 0x7f, 0x2f, 0xf0, 0xc8, 0x8b, 0x54, 0x1b, 0xa9, 0x4d, 0x98, 0x6d, 0x15, 0x48, + 0x91, 0x2e, 0x11, 0xc3, 0xab, 0x69, 0x58, 0x40, 0x09, 0xd2, 0x04, 0x45, 0xa9, 0xad, 0xa6, 0x47, + 0x8d, 0x24, 0xb8, 0x97, 0x04, 0x57, 0xd3, 0x93, 0xe7, 0x4d, 0x37, 0x76, 0x9a, 0xb0, 0x95, 0xb8, + 0xe2, 0xe4, 0x88, 0x6b, 0xae, 0x9b, 0x7e, 0xfd, 0xd6, 0x74, 0x5f, 0xfe, 0xec, 0x93, 0xc1, 0xa5, + 0xe3, 0xd2, 0x05, 0xe9, 0x43, 0x5e, 0xac, 0x80, 0x79, 0x63, 0x6f, 0x32, 0x9a, 0x4d, 0x6f, 0xee, + 0xce, 0x7a, 0xdf, 0xef, 0xce, 0x4e, 0x1b, 0x8a, 0xc9, 0xd6, 0x81, 0xd0, 0xa1, 0x04, 0xbb, 0x0a, + 0x2e, 0x90, 0x43, 0xba, 0x9d, 0x63, 0xfa, 0xf5, 0xcb, 0x39, 0x69, 0x43, 0xe6, 0x98, 0x46, 0x8d, + 0x9f, 0xbe, 0x26, 0x07, 0x09, 0x5a, 0x60, 0x0f, 0xba, 0x72, 0x9c, 0xbd, 0x9e, 0x87, 0x83, 0x94, + 0xc0, 0x1e, 0x76, 0x9e, 0xc7, 0xf9, 0xe9, 0x47, 0x42, 0xa5, 0x50, 0x71, 0x02, 0x06, 0x63, 0x0e, + 0xf5, 0xcf, 0x11, 0x29, 0xb2, 0x7e, 0x57, 0xea, 0x13, 0x29, 0xd4, 0x0c, 0x0c, 0x2e, 0xc0, 0x5c, + 0xd6, 0x24, 0xba, 0x26, 0xcc, 0x42, 0xc9, 0xd1, 0xc6, 0x49, 0xae, 0xd3, 0x75, 0xbc, 0xb1, 0x22, + 0x17, 0x9f, 0xc0, 0x0a, 0xad, 0xd8, 0xa0, 0x6b, 0xca, 0x71, 0x83, 0x9c, 0xd5, 0xc4, 0x77, 0xf7, + 0x40, 0x3a, 0x25, 0xcf, 0x32, 0x5c, 0xc2, 0x26, 0xb7, 0xb1, 0x84, 0xaa, 0x4d, 0xe4, 0x60, 0xd8, + 0xe1, 0xd8, 0x9b, 0x1c, 0x44, 0xb4, 0xfd, 0xf8, 0x16, 0x2a, 0x67, 0x5d, 0x80, 0xa1, 0x1f, 0xc8, + 0xd3, 0x7a, 0xff, 0x1c, 0xa1, 0x54, 0x42, 0xf1, 0xb8, 0x04, 0x8b, 0x6c, 0xf8, 0x3f, 0xeb, 0x5f, + 0xb4, 0xa8, 0x08, 0x2c, 0x3a, 0x3c, 0x54, 0xff, 0xe0, 0x47, 0xdd, 0xf1, 0x50, 0xfd, 0x85, 0x3f, + 0x26, 0x83, 0x6b, 0xa1, 0x32, 0x7d, 0xcd, 0x88, 0xdb, 0xb0, 0xad, 0xe8, 0x29, 0x19, 0x2d, 0x11, + 0xe3, 0x0c, 0x95, 0x96, 0xec, 0x51, 0x1d, 0x17, 0x0d, 0x97, 0x88, 0xf3, 0xba, 0xa6, 0x8c, 0x1c, + 0xa2, 0x82, 0x24, 0xc7, 0x8c, 0x3d, 0x1e, 0x7b, 0x93, 0x61, 0xf4, 0xbb, 0x9c, 0xbd, 0xb9, 0xd9, + 0xf9, 0xde, 0xed, 0xce, 0xf7, 0x7e, 0xec, 0x7c, 0xef, 0xf3, 0xde, 0xef, 0xdd, 0xee, 0xfd, 0xde, + 0xb7, 0xbd, 0xdf, 0x7b, 0x1f, 0x72, 0x61, 0x57, 0x9b, 0x24, 0x48, 0xb5, 0x6c, 0x2f, 0xa7, 0x7d, + 0x9c, 0x9b, 0x6c, 0x1d, 0x56, 0x7f, 0x1e, 0xa3, 0xdd, 0x16, 0x68, 0x92, 0x81, 0x3b, 0xa1, 0x57, + 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x0d, 0xa8, 0x58, 0xaa, 0xae, 0x03, 0x00, 0x00, +} + +func (m *Params) 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 *Params) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Enabled { + i-- + if m.Enabled { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x60 + } + if len(m.FeeDenom) > 0 { + i -= len(m.FeeDenom) + copy(dAtA[i:], m.FeeDenom) + i = encodeVarintParams(dAtA, i, uint64(len(m.FeeDenom))) + i-- + dAtA[i] = 0x5a + } + if m.Window != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.Window)) + i-- + dAtA[i] = 0x50 + } + { + size := m.MaxLearningRate.Size() + i -= size + if _, err := m.MaxLearningRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x4a + { + size := m.MinLearningRate.Size() + i -= size + if _, err := m.MinLearningRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x42 + if m.DefaultMaxBlockGas != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.DefaultMaxBlockGas)) + i-- + dAtA[i] = 0x38 + } + { + size := m.TargetBlockUtilization.Size() + i -= size + if _, err := m.TargetBlockUtilization.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + { + size := m.MinBaseGasPrice.Size() + i -= size + if _, err := m.MinBaseGasPrice.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + { + size := m.Gamma.Size() + i -= size + if _, err := m.Gamma.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + { + size := m.Beta.Size() + i -= size + if _, err := m.Beta.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size := m.Alpha.Size() + i -= size + if _, err := m.Alpha.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func encodeVarintParams(dAtA []byte, offset int, v uint64) int { + offset -= sovParams(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *Params) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Alpha.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.Beta.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.Gamma.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.MinBaseGasPrice.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.TargetBlockUtilization.Size() + n += 1 + l + sovParams(uint64(l)) + if m.DefaultMaxBlockGas != 0 { + n += 1 + sovParams(uint64(m.DefaultMaxBlockGas)) + } + l = m.MinLearningRate.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.MaxLearningRate.Size() + n += 1 + l + sovParams(uint64(l)) + if m.Window != 0 { + n += 1 + sovParams(uint64(m.Window)) + } + l = len(m.FeeDenom) + if l > 0 { + n += 1 + l + sovParams(uint64(l)) + } + if m.Enabled { + n += 2 + } + return n +} + +func sovParams(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozParams(x uint64) (n int) { + return sovParams(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Params) 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 ErrIntOverflowParams + } + 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: Params: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Alpha", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Alpha.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Beta", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Beta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gamma", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Gamma.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinBaseGasPrice", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinBaseGasPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TargetBlockUtilization", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TargetBlockUtilization.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DefaultMaxBlockGas", wireType) + } + m.DefaultMaxBlockGas = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.DefaultMaxBlockGas |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinLearningRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinLearningRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxLearningRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MaxLearningRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Window", wireType) + } + m.Window = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Window |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FeeDenom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FeeDenom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Enabled", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Enabled = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipParams(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, ErrIntOverflowParams + } + 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, ErrIntOverflowParams + } + 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, ErrIntOverflowParams + } + 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, ErrInvalidLengthParams + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupParams + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthParams + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthParams = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowParams = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupParams = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/dynamicfee/types/params_test.go b/x/dynamicfee/types/params_test.go new file mode 100644 index 000000000000..4f90344a3677 --- /dev/null +++ b/x/dynamicfee/types/params_test.go @@ -0,0 +1,299 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func TestParams(t *testing.T) { + testCases := []struct { + name string + p types.Params + expectedErr bool + }{ + { + name: "valid base eip-1559 params", + p: types.DefaultParams(), + expectedErr: false, + }, + { + name: "valid aimd eip-1559 params", + p: types.DefaultAIMDParams(), + expectedErr: false, + }, + { + name: "invalid window", + p: types.Params{}, + expectedErr: true, + }, + { + name: "nil alpha", + p: types.Params{ + Window: 1, + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "negative alpha", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("-0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "beta is nil", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "beta is negative", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("-0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "beta is greater than 1", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("1.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "theta is nil", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "theta is negative", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("-0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "theta is greater than 1", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("1.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "target block size is zero", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "max block size is zero", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "min base gas price is nil", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "min base has price is negative", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("-1.0"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "target block utilization is nil", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "target block utilization is negative", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + TargetBlockUtilization: math.LegacyMustNewDecFromStr("-0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "target block utilization is zero", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + TargetBlockUtilization: math.LegacyMustNewDecFromStr("0"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "target block utilization is greater than 1", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + TargetBlockUtilization: math.LegacyMustNewDecFromStr("1.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "min learning rate is nil", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "min learning rate is negative", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + MinLearningRate: math.LegacyMustNewDecFromStr("-0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "max learning rate is nil", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + MinLearningRate: math.LegacyMustNewDecFromStr("0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "max learning rate is negative", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + MinLearningRate: math.LegacyMustNewDecFromStr("0.1"), + MaxLearningRate: math.LegacyMustNewDecFromStr("-0.1"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "min learning rate is greater than max learning rate", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + MinLearningRate: math.LegacyMustNewDecFromStr("0.1"), + MaxLearningRate: math.LegacyMustNewDecFromStr("0.05"), + FeeDenom: types.DefaultFeeDenom, + }, + expectedErr: true, + }, + { + name: "fee denom is empty", + p: types.Params{ + Window: 1, + Alpha: math.LegacyMustNewDecFromStr("0.1"), + Beta: math.LegacyMustNewDecFromStr("0.1"), + Gamma: math.LegacyMustNewDecFromStr("0.1"), + MinBaseGasPrice: math.LegacyMustNewDecFromStr("1.0"), + MinLearningRate: math.LegacyMustNewDecFromStr("0.01"), + MaxLearningRate: math.LegacyMustNewDecFromStr("0.05"), + }, + expectedErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.p.ValidateBasic() + if tc.expectedErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/dynamicfee/types/query.pb.go b/x/dynamicfee/types/query.pb.go new file mode 100644 index 000000000000..e32cc75209fd --- /dev/null +++ b/x/dynamicfee/types/query.pb.go @@ -0,0 +1,1615 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: cosmos/dynamicfee/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + 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 + +// ParamsRequest is the request type for the Query/Params RPC method. +type ParamsRequest struct { +} + +func (m *ParamsRequest) Reset() { *m = ParamsRequest{} } +func (m *ParamsRequest) String() string { return proto.CompactTextString(m) } +func (*ParamsRequest) ProtoMessage() {} +func (*ParamsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{0} +} +func (m *ParamsRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ParamsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ParamsRequest.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 *ParamsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ParamsRequest.Merge(m, src) +} +func (m *ParamsRequest) XXX_Size() int { + return m.Size() +} +func (m *ParamsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ParamsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_ParamsRequest proto.InternalMessageInfo + +// ParamsResponse is the response type for the Query/Params RPC method. +type ParamsResponse struct { + Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` +} + +func (m *ParamsResponse) Reset() { *m = ParamsResponse{} } +func (m *ParamsResponse) String() string { return proto.CompactTextString(m) } +func (*ParamsResponse) ProtoMessage() {} +func (*ParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{1} +} +func (m *ParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ParamsResponse.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 *ParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_ParamsResponse.Merge(m, src) +} +func (m *ParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *ParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_ParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_ParamsResponse proto.InternalMessageInfo + +func (m *ParamsResponse) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// StateRequest is the request type for the Query/State RPC method. +type StateRequest struct { +} + +func (m *StateRequest) Reset() { *m = StateRequest{} } +func (m *StateRequest) String() string { return proto.CompactTextString(m) } +func (*StateRequest) ProtoMessage() {} +func (*StateRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{2} +} +func (m *StateRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StateRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StateRequest.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 *StateRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_StateRequest.Merge(m, src) +} +func (m *StateRequest) XXX_Size() int { + return m.Size() +} +func (m *StateRequest) XXX_DiscardUnknown() { + xxx_messageInfo_StateRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_StateRequest proto.InternalMessageInfo + +// StateResponse is the response type for the Query/State RPC method. +type StateResponse struct { + State State `protobuf:"bytes,1,opt,name=state,proto3" json:"state"` +} + +func (m *StateResponse) Reset() { *m = StateResponse{} } +func (m *StateResponse) String() string { return proto.CompactTextString(m) } +func (*StateResponse) ProtoMessage() {} +func (*StateResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{3} +} +func (m *StateResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *StateResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_StateResponse.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 *StateResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_StateResponse.Merge(m, src) +} +func (m *StateResponse) XXX_Size() int { + return m.Size() +} +func (m *StateResponse) XXX_DiscardUnknown() { + xxx_messageInfo_StateResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_StateResponse proto.InternalMessageInfo + +func (m *StateResponse) GetState() State { + if m != nil { + return m.State + } + return State{} +} + +// GasPriceRequest is the request type for the Query/GasPrice RPC method. +type GasPriceRequest struct { + // denom we are querying gas price in + Denom string `protobuf:"bytes,1,opt,name=denom,proto3" json:"denom,omitempty"` +} + +func (m *GasPriceRequest) Reset() { *m = GasPriceRequest{} } +func (m *GasPriceRequest) String() string { return proto.CompactTextString(m) } +func (*GasPriceRequest) ProtoMessage() {} +func (*GasPriceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{4} +} +func (m *GasPriceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GasPriceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GasPriceRequest.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 *GasPriceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GasPriceRequest.Merge(m, src) +} +func (m *GasPriceRequest) XXX_Size() int { + return m.Size() +} +func (m *GasPriceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GasPriceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GasPriceRequest proto.InternalMessageInfo + +func (m *GasPriceRequest) GetDenom() string { + if m != nil { + return m.Denom + } + return "" +} + +// GasPriceResponse is the response type for the Query/GasPrice RPC method. +// Returns a gas price in specified denom. +type GasPriceResponse struct { + Price types.DecCoin `protobuf:"bytes,1,opt,name=price,proto3" json:"price"` +} + +func (m *GasPriceResponse) Reset() { *m = GasPriceResponse{} } +func (m *GasPriceResponse) String() string { return proto.CompactTextString(m) } +func (*GasPriceResponse) ProtoMessage() {} +func (*GasPriceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{5} +} +func (m *GasPriceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GasPriceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GasPriceResponse.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 *GasPriceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GasPriceResponse.Merge(m, src) +} +func (m *GasPriceResponse) XXX_Size() int { + return m.Size() +} +func (m *GasPriceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GasPriceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GasPriceResponse proto.InternalMessageInfo + +func (m *GasPriceResponse) GetPrice() types.DecCoin { + if m != nil { + return m.Price + } + return types.DecCoin{} +} + +// GasPriceRequest is the request type for the Query/GasPrices RPC method. +type GasPricesRequest struct { +} + +func (m *GasPricesRequest) Reset() { *m = GasPricesRequest{} } +func (m *GasPricesRequest) String() string { return proto.CompactTextString(m) } +func (*GasPricesRequest) ProtoMessage() {} +func (*GasPricesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{6} +} +func (m *GasPricesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GasPricesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GasPricesRequest.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 *GasPricesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GasPricesRequest.Merge(m, src) +} +func (m *GasPricesRequest) XXX_Size() int { + return m.Size() +} +func (m *GasPricesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GasPricesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GasPricesRequest proto.InternalMessageInfo + +// GasPricesResponse is the response type for the Query/GasPrices RPC method. +// Returns a gas price in all available denoms. +type GasPricesResponse struct { + Prices github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,1,rep,name=prices,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"prices"` +} + +func (m *GasPricesResponse) Reset() { *m = GasPricesResponse{} } +func (m *GasPricesResponse) String() string { return proto.CompactTextString(m) } +func (*GasPricesResponse) ProtoMessage() {} +func (*GasPricesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_15ac558d9017ea74, []int{7} +} +func (m *GasPricesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GasPricesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GasPricesResponse.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 *GasPricesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GasPricesResponse.Merge(m, src) +} +func (m *GasPricesResponse) XXX_Size() int { + return m.Size() +} +func (m *GasPricesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GasPricesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GasPricesResponse proto.InternalMessageInfo + +func (m *GasPricesResponse) GetPrices() github_com_cosmos_cosmos_sdk_types.DecCoins { + if m != nil { + return m.Prices + } + return nil +} + +func init() { + proto.RegisterType((*ParamsRequest)(nil), "cosmos.dynamicfee.v1.ParamsRequest") + proto.RegisterType((*ParamsResponse)(nil), "cosmos.dynamicfee.v1.ParamsResponse") + proto.RegisterType((*StateRequest)(nil), "cosmos.dynamicfee.v1.StateRequest") + proto.RegisterType((*StateResponse)(nil), "cosmos.dynamicfee.v1.StateResponse") + proto.RegisterType((*GasPriceRequest)(nil), "cosmos.dynamicfee.v1.GasPriceRequest") + proto.RegisterType((*GasPriceResponse)(nil), "cosmos.dynamicfee.v1.GasPriceResponse") + proto.RegisterType((*GasPricesRequest)(nil), "cosmos.dynamicfee.v1.GasPricesRequest") + proto.RegisterType((*GasPricesResponse)(nil), "cosmos.dynamicfee.v1.GasPricesResponse") +} + +func init() { proto.RegisterFile("cosmos/dynamicfee/v1/query.proto", fileDescriptor_15ac558d9017ea74) } + +var fileDescriptor_15ac558d9017ea74 = []byte{ + // 551 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0x41, 0x6b, 0x13, 0x41, + 0x18, 0xcd, 0x6a, 0x37, 0x98, 0xd1, 0xb6, 0x76, 0xc8, 0x41, 0xd2, 0xb8, 0x8d, 0x93, 0x6a, 0xaa, + 0xe2, 0x0e, 0xa9, 0x07, 0x45, 0xf0, 0x12, 0x05, 0x15, 0x3c, 0xb4, 0xf1, 0xe6, 0x45, 0x26, 0x9b, + 0x71, 0x5d, 0x74, 0x67, 0xb6, 0x99, 0x4d, 0x31, 0x88, 0x17, 0x05, 0x0f, 0x5e, 0x14, 0xfc, 0x13, + 0xe2, 0xc9, 0x9f, 0xd1, 0x63, 0xc1, 0x8b, 0x27, 0x95, 0x44, 0xf0, 0x6f, 0xc8, 0xcc, 0x7c, 0x9b, + 0x94, 0xb0, 0xec, 0x5e, 0x92, 0xec, 0xb7, 0xef, 0x7b, 0xef, 0xcd, 0xbc, 0x47, 0x50, 0x2b, 0x90, + 0x2a, 0x96, 0x8a, 0x0e, 0x27, 0x82, 0xc5, 0x51, 0xf0, 0x9c, 0x73, 0x7a, 0xd8, 0xa5, 0x07, 0x63, + 0x3e, 0x9a, 0xf8, 0xc9, 0x48, 0xa6, 0x12, 0xd7, 0x2d, 0xc2, 0x5f, 0x20, 0xfc, 0xc3, 0x6e, 0xa3, + 0x1e, 0xca, 0x50, 0x1a, 0x00, 0xd5, 0xbf, 0x2c, 0xb6, 0xd1, 0x0c, 0xa5, 0x0c, 0x5f, 0x71, 0xca, + 0x92, 0x88, 0x32, 0x21, 0x64, 0xca, 0xd2, 0x48, 0x0a, 0x05, 0x6f, 0x3d, 0xd0, 0x1a, 0x30, 0xa5, + 0x55, 0x06, 0x3c, 0x65, 0x5d, 0x1a, 0xc8, 0x48, 0xc0, 0xfb, 0x0d, 0x16, 0x47, 0x42, 0x52, 0xf3, + 0x09, 0xa3, 0x4b, 0xb9, 0xf6, 0x12, 0x36, 0x62, 0x71, 0xc6, 0x4a, 0x72, 0x21, 0x21, 0x17, 0x5c, + 0x45, 0x80, 0x21, 0xeb, 0x68, 0x75, 0xcf, 0xec, 0xf4, 0xf9, 0xc1, 0x98, 0xab, 0x94, 0x3c, 0x46, + 0x6b, 0xd9, 0x40, 0x25, 0x52, 0x28, 0x8e, 0xef, 0xa0, 0xaa, 0xa5, 0xbd, 0xe0, 0xb4, 0x9c, 0x9d, + 0xb3, 0xbb, 0x4d, 0x3f, 0xef, 0xdc, 0xbe, 0xdd, 0xea, 0xad, 0x1c, 0xfd, 0xda, 0xaa, 0xf4, 0x61, + 0x83, 0xac, 0xa1, 0x73, 0x4f, 0x52, 0x96, 0xf2, 0x8c, 0xfd, 0x21, 0x5a, 0x85, 0x67, 0x20, 0xbf, + 0x85, 0x5c, 0xa5, 0x07, 0xc0, 0xbd, 0x99, 0xcf, 0x6d, 0x76, 0x80, 0xda, 0xe2, 0x49, 0x07, 0xad, + 0x3f, 0x60, 0x6a, 0x6f, 0x14, 0x05, 0x19, 0x39, 0xae, 0x23, 0x77, 0xc8, 0x85, 0x8c, 0x0d, 0x57, + 0xad, 0x6f, 0x1f, 0xc8, 0x3e, 0x3a, 0xbf, 0x00, 0x82, 0xea, 0x5d, 0xe4, 0x26, 0x7a, 0xb0, 0x7c, + 0x22, 0x7d, 0xff, 0x3e, 0xdc, 0xbf, 0x7f, 0x9f, 0x07, 0xf7, 0x64, 0x24, 0x7a, 0x35, 0x2d, 0xfb, + 0xf5, 0xdf, 0xf7, 0x6b, 0x4e, 0xdf, 0x6e, 0x11, 0xbc, 0xa0, 0x9c, 0xdf, 0xdb, 0x7b, 0x07, 0x6d, + 0x9c, 0x18, 0x82, 0x90, 0x40, 0x55, 0xb3, 0xa2, 0xef, 0xee, 0x74, 0xa9, 0xd2, 0x6d, 0xad, 0xf4, + 0xed, 0xf7, 0xd6, 0xf5, 0x30, 0x4a, 0x5f, 0x8c, 0x07, 0x7e, 0x20, 0x63, 0x0a, 0x19, 0xda, 0xaf, + 0x1b, 0x6a, 0xf8, 0x92, 0xa6, 0x93, 0x84, 0xab, 0x6c, 0x47, 0x59, 0x63, 0xa0, 0xb2, 0xfb, 0x69, + 0x05, 0xb9, 0xfb, 0xba, 0xa2, 0x78, 0x82, 0xaa, 0x36, 0x11, 0xdc, 0x2e, 0xca, 0x0b, 0xec, 0x37, + 0xb6, 0x8b, 0x41, 0xf6, 0x38, 0x64, 0xfb, 0xdd, 0x8f, 0xbf, 0x5f, 0x4e, 0x79, 0xb8, 0x49, 0x0b, + 0xda, 0x87, 0xc7, 0xc8, 0x35, 0x81, 0x61, 0x52, 0x90, 0x66, 0x26, 0xdc, 0x2e, 0xc4, 0x80, 0x6e, + 0xdb, 0xe8, 0x5e, 0xc4, 0x9b, 0xf9, 0xba, 0xa6, 0x11, 0xf8, 0xa3, 0x83, 0xce, 0x64, 0x09, 0xe0, + 0xcb, 0xf9, 0xb4, 0x4b, 0x95, 0x69, 0x5c, 0x29, 0x83, 0x81, 0x01, 0x6a, 0x0c, 0x5c, 0xc5, 0x9d, + 0x7c, 0x03, 0x21, 0x53, 0xcf, 0x4c, 0x02, 0xf4, 0x8d, 0x29, 0xdd, 0x5b, 0xfc, 0xc1, 0x41, 0xb5, + 0x79, 0x1d, 0x70, 0x89, 0xcc, 0x3c, 0x85, 0x4e, 0x29, 0x0e, 0xfc, 0xec, 0x18, 0x3f, 0x04, 0xb7, + 0x4a, 0xfc, 0xa8, 0xde, 0xa3, 0xa3, 0xa9, 0xe7, 0x1c, 0x4f, 0x3d, 0xe7, 0xcf, 0xd4, 0x73, 0x3e, + 0xcf, 0xbc, 0xca, 0xf1, 0xcc, 0xab, 0xfc, 0x9c, 0x79, 0x95, 0xa7, 0xb4, 0xb0, 0x65, 0xaf, 0x4f, + 0x52, 0x9a, 0xca, 0x0d, 0xaa, 0xe6, 0x2f, 0xe3, 0xe6, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0x56, + 0x85, 0x19, 0xe4, 0x1a, 0x05, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Params returns the current dynamicfee module parameters. + Params(ctx context.Context, in *ParamsRequest, opts ...grpc.CallOption) (*ParamsResponse, error) + // State returns the current dynamicfee module state. + State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) + // GasPrice returns the current dynamicfee module gas price + // for specified denom. + GasPrice(ctx context.Context, in *GasPriceRequest, opts ...grpc.CallOption) (*GasPriceResponse, error) + // GasPrices returns the current dynamicfee module list of gas prices + // in all available denoms. + GasPrices(ctx context.Context, in *GasPricesRequest, opts ...grpc.CallOption) (*GasPricesResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Params(ctx context.Context, in *ParamsRequest, opts ...grpc.CallOption) (*ParamsResponse, error) { + out := new(ParamsResponse) + err := c.cc.Invoke(ctx, "/cosmos.dynamicfee.v1.Query/Params", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) State(ctx context.Context, in *StateRequest, opts ...grpc.CallOption) (*StateResponse, error) { + out := new(StateResponse) + err := c.cc.Invoke(ctx, "/cosmos.dynamicfee.v1.Query/State", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) GasPrice(ctx context.Context, in *GasPriceRequest, opts ...grpc.CallOption) (*GasPriceResponse, error) { + out := new(GasPriceResponse) + err := c.cc.Invoke(ctx, "/cosmos.dynamicfee.v1.Query/GasPrice", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) GasPrices(ctx context.Context, in *GasPricesRequest, opts ...grpc.CallOption) (*GasPricesResponse, error) { + out := new(GasPricesResponse) + err := c.cc.Invoke(ctx, "/cosmos.dynamicfee.v1.Query/GasPrices", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Params returns the current dynamicfee module parameters. + Params(context.Context, *ParamsRequest) (*ParamsResponse, error) + // State returns the current dynamicfee module state. + State(context.Context, *StateRequest) (*StateResponse, error) + // GasPrice returns the current dynamicfee module gas price + // for specified denom. + GasPrice(context.Context, *GasPriceRequest) (*GasPriceResponse, error) + // GasPrices returns the current dynamicfee module list of gas prices + // in all available denoms. + GasPrices(context.Context, *GasPricesRequest) (*GasPricesResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Params(ctx context.Context, req *ParamsRequest) (*ParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Params not implemented") +} +func (*UnimplementedQueryServer) State(ctx context.Context, req *StateRequest) (*StateResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method State not implemented") +} +func (*UnimplementedQueryServer) GasPrice(ctx context.Context, req *GasPriceRequest) (*GasPriceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GasPrice not implemented") +} +func (*UnimplementedQueryServer) GasPrices(ctx context.Context, req *GasPricesRequest) (*GasPricesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GasPrices not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Params_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ParamsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Params(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cosmos.dynamicfee.v1.Query/Params", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Params(ctx, req.(*ParamsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_State_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(StateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).State(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cosmos.dynamicfee.v1.Query/State", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).State(ctx, req.(*StateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_GasPrice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GasPriceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).GasPrice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cosmos.dynamicfee.v1.Query/GasPrice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).GasPrice(ctx, req.(*GasPriceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_GasPrices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GasPricesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).GasPrices(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cosmos.dynamicfee.v1.Query/GasPrices", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).GasPrices(ctx, req.(*GasPricesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var Query_serviceDesc = _Query_serviceDesc +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "cosmos.dynamicfee.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Params", + Handler: _Query_Params_Handler, + }, + { + MethodName: "State", + Handler: _Query_State_Handler, + }, + { + MethodName: "GasPrice", + Handler: _Query_GasPrice_Handler, + }, + { + MethodName: "GasPrices", + Handler: _Query_GasPrices_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "cosmos/dynamicfee/v1/query.proto", +} + +func (m *ParamsRequest) 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 *ParamsRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ParamsRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *ParamsResponse) 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 *ParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *StateRequest) 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 *StateRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StateRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *StateResponse) 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 *StateResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *StateResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.State.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GasPriceRequest) 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 *GasPriceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GasPriceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Denom) > 0 { + i -= len(m.Denom) + copy(dAtA[i:], m.Denom) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Denom))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *GasPriceResponse) 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 *GasPriceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GasPriceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Price.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *GasPricesRequest) 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 *GasPricesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GasPricesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *GasPricesResponse) 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 *GasPricesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GasPricesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Prices) > 0 { + for iNdEx := len(m.Prices) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Prices[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ParamsRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *ParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Params.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *StateRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *StateResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.State.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *GasPriceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Denom) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *GasPriceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Price.Size() + n += 1 + l + sovQuery(uint64(l)) + return n +} + +func (m *GasPricesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *GasPricesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Prices) > 0 { + for _, e := range m.Prices { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ParamsRequest) 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 ErrIntOverflowQuery + } + 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: ParamsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ParamsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ParamsResponse) 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 ErrIntOverflowQuery + } + 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: ParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StateRequest) 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 ErrIntOverflowQuery + } + 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: StateRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StateRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StateResponse) 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 ErrIntOverflowQuery + } + 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: StateResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StateResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.State.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GasPriceRequest) 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 ErrIntOverflowQuery + } + 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: GasPriceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GasPriceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Denom", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Denom = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GasPriceResponse) 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 ErrIntOverflowQuery + } + 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: GasPriceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GasPriceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Price", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Price.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GasPricesRequest) 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 ErrIntOverflowQuery + } + 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: GasPricesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GasPricesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GasPricesResponse) 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 ErrIntOverflowQuery + } + 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: GasPricesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GasPricesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prices = append(m.Prices, types.DecCoin{}) + if err := m.Prices[len(m.Prices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(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, ErrIntOverflowQuery + } + 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, ErrIntOverflowQuery + } + 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, ErrIntOverflowQuery + } + 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, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/dynamicfee/types/query.pb.gw.go b/x/dynamicfee/types/query.pb.gw.go new file mode 100644 index 000000000000..884502bc8b4b --- /dev/null +++ b/x/dynamicfee/types/query.pb.gw.go @@ -0,0 +1,384 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: cosmos/dynamicfee/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage +var _ = metadata.Join + +func request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ParamsRequest + var metadata runtime.ServerMetadata + + msg, err := client.Params(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Params_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ParamsRequest + var metadata runtime.ServerMetadata + + msg, err := server.Params(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_State_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StateRequest + var metadata runtime.ServerMetadata + + msg, err := client.State(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_State_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq StateRequest + var metadata runtime.ServerMetadata + + msg, err := server.State(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_GasPrice_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GasPriceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + msg, err := client.GasPrice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_GasPrice_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GasPriceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["denom"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "denom") + } + + protoReq.Denom, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "denom", err) + } + + msg, err := server.GasPrice(ctx, &protoReq) + return msg, metadata, err + +} + +func request_Query_GasPrices_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GasPricesRequest + var metadata runtime.ServerMetadata + + msg, err := client.GasPrices(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_GasPrices_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GasPricesRequest + var metadata runtime.ServerMetadata + + msg, err := server.GasPrices(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Params_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_State_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_State_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_State_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_GasPrice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_GasPrice_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_GasPrice_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_GasPrices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_GasPrices_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_GasPrices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Params_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Params_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Params_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_State_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_State_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_State_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_GasPrice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_GasPrice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_GasPrice_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_GasPrices_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_GasPrices_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_GasPrices_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Params_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"cosmos", "dynamicfee", "v1", "params"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_State_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"cosmos", "dynamicfee", "v1", "state"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_GasPrice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"cosmos", "dynamicfee", "v1", "gas_price", "denom"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Query_GasPrices_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"cosmos", "dynamicfee", "v1", "gas_prices"}, "", runtime.AssumeColonVerbOpt(false))) +) + +var ( + forward_Query_Params_0 = runtime.ForwardResponseMessage + + forward_Query_State_0 = runtime.ForwardResponseMessage + + forward_Query_GasPrice_0 = runtime.ForwardResponseMessage + + forward_Query_GasPrices_0 = runtime.ForwardResponseMessage +) diff --git a/x/dynamicfee/types/resolver.go b/x/dynamicfee/types/resolver.go new file mode 100644 index 000000000000..10661fd6adf7 --- /dev/null +++ b/x/dynamicfee/types/resolver.go @@ -0,0 +1,50 @@ +package types + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// DenomResolver is an interface to convert a given token to the dynamicfee's base token. +type DenomResolver interface { + // ConvertToDenom converts deccoin into the equivalent amount of the token denominated in denom. + ConvertToDenom(ctx context.Context, coin sdk.DecCoin, denom string) (sdk.DecCoin, error) + // ExtraDenoms returns a list of denoms in addition of `Params.base_denom` it's possible to pay fees with + ExtraDenoms(ctx context.Context) ([]string, error) +} + +// TestDenomResolver is a test implementation of the DenomResolver interface. It returns "feeCoin.Amount baseDenom" for all coins that are not the baseDenom. +// NOTE: DO NOT USE THIS IN PRODUCTION +type TestDenomResolver struct{} + +// ConvertToDenom returns "coin.Amount denom" for all coins that are not the denom. +func (r *TestDenomResolver) ConvertToDenom(_ context.Context, coin sdk.DecCoin, denom string) (sdk.DecCoin, error) { + if coin.Denom == denom { + return coin, nil + } + + return sdk.NewDecCoinFromDec(denom, coin.Amount), nil +} + +func (r *TestDenomResolver) ExtraDenoms(_ context.Context) ([]string, error) { + return []string{}, nil +} + +// ErrorDenomResolver is a test implementation of the DenomResolver interface. It returns an error for all coins that are not the baseDenom. +// NOTE: DO NOT USE THIS IN PRODUCTION +type ErrorDenomResolver struct{} + +// ConvertToDenom returns an error for all coins that are not the denom. +func (r *ErrorDenomResolver) ConvertToDenom(_ context.Context, coin sdk.DecCoin, denom string) (sdk.DecCoin, error) { + if coin.Denom == denom { + return coin, nil + } + + return sdk.DecCoin{}, fmt.Errorf("error resolving denom") +} + +func (r *ErrorDenomResolver) ExtraDenoms(_ context.Context) ([]string, error) { + return []string{}, nil +} diff --git a/x/dynamicfee/types/state.go b/x/dynamicfee/types/state.go new file mode 100644 index 000000000000..df70f18bb7d2 --- /dev/null +++ b/x/dynamicfee/types/state.go @@ -0,0 +1,176 @@ +package types + +import ( + fmt "fmt" + + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/log" + "cosmossdk.io/math" +) + +// NewState instantiates a new dynamicfee state instance. This is utilized +// to implement both the base EIP-1559 dynamic fee pricing implementation and +// the AIMD EIP-1559 dynamic fee pricing implementation. Note that on init, +// you initialize both the minimum and current base gas price to the same value. +func NewState( + windowSize uint64, + baseGasPrice math.LegacyDec, + learningRate math.LegacyDec, +) State { + return State{ + Window: make([]uint64, windowSize), + BaseGasPrice: baseGasPrice, + Index: 0, + LearningRate: learningRate, + } +} + +// Update updates the block gas for the current height with the given +// transaction gas i.e. gas limit. +func (s *State) Update(gas, maxBlockGas uint64) error { + update := s.Window[s.Index] + gas + if update > maxBlockGas { + return errorsmod.Wrapf(ErrMaxGasExceeded, "gas %d > max %d", update, maxBlockGas) + } + + s.Window[s.Index] = update + return nil +} + +// IncrementHeight increments the current height of the state. +func (s *State) IncrementHeight() { + s.Index = (s.Index + 1) % uint64(len(s.Window)) + s.Window[s.Index] = 0 +} + +// UpdateBaseGasPrice updates the learning rate and base gas price based on the AIMD +// learning rate adjustment algorithm. The learning rate is updated +// based on the average gas of the block window. The base gas price is +// update using the new learning rate. Please see the EIP-1559 specification +// for more details. +func (s *State) UpdateBaseGasPrice(logger log.Logger, params Params, maxBlockGas uint64) (gasPrice math.LegacyDec) { + // Panic catch in case there is an overflow + defer func() { + if rec := recover(); rec != nil { + logger.Error("Panic recovered in state.UpdateBaseGasPrice", "err", rec) + s.BaseGasPrice = params.MinBaseGasPrice + gasPrice = s.BaseGasPrice + } + }() + // Calculate the new base gasPrice with the learning rate adjustment. + currentBlockGas := math.LegacyNewDecFromInt(math.NewIntFromUint64(s.Window[s.Index])) + targetBlockGas := math.LegacyNewDecFromInt(math.NewIntFromUint64(GetTargetBlockGas(maxBlockGas, params))) + avgGas := (currentBlockGas.Sub(targetBlockGas)).Quo(targetBlockGas) + + // Truncate the learning rate adjustment to an integer. + // + // This is equivalent to + // 1 + (learningRate * (currentBlockGas - targetBlockGas) / targetBlockGas) + learningRateAdjustment := math.LegacyOneDec().Add(s.LearningRate.Mul(avgGas)) + + // Update the base gasPrice. + gasPrice = s.BaseGasPrice.Mul(learningRateAdjustment) + + // Ensure the base gasPrice is greater than the minimum base gasPrice. + if gasPrice.LT(params.MinBaseGasPrice) { + gasPrice = params.MinBaseGasPrice + } + + s.BaseGasPrice = gasPrice + return s.BaseGasPrice +} + +// UpdateLearningRate updates the learning rate based on the AIMD +// learning rate adjustment algorithm. The learning rate is updated +// based on the average gas of the block window. There are +// two cases that can occur: +// +// 1. The average gas is above the target threshold. In this +// case, the learning rate is increased by the alpha parameter. This occurs +// when blocks are nearly full or empty. +// 2. The average gas is below the target threshold. In this +// case, the learning rate is decreased by the beta parameter. This occurs +// when blocks are relatively close to the target block gas. +// +// For more details, please see the EIP-1559 specification. +func (s *State) UpdateLearningRate(params Params, maxBlockGas uint64) (lr math.LegacyDec) { + // Panic catch in case there is an overflow + defer func() { + if rec := recover(); rec != nil { + s.LearningRate = params.MinLearningRate + lr = s.LearningRate + } + }() + + // Calculate the average gas of the block window. + avg := s.GetAverageGas(maxBlockGas) + + // Determine if the average gas is above or below the target + // threshold and adjust the learning rate accordingly. + if avg.LTE(params.Gamma) || avg.GTE(math.LegacyOneDec().Sub(params.Gamma)) { + lr = params.Alpha.Add(s.LearningRate) + if lr.GT(params.MaxLearningRate) { + lr = params.MaxLearningRate + } + } else { + lr = s.LearningRate.Mul(params.Beta) + if lr.LT(params.MinLearningRate) { + lr = params.MinLearningRate + } + } + + // Update the current learning rate. + s.LearningRate = lr + return s.LearningRate +} + +// GetNetGas returns the net gas of the block window. +func (s *State) GetNetGas(maxBlockGas uint64, params Params) math.Int { + net := math.NewInt(0) + + targetGas := math.NewIntFromUint64(GetTargetBlockGas(maxBlockGas, params)) + for _, gas := range s.Window { + diff := math.NewIntFromUint64(gas).Sub(targetGas) + net = net.Add(diff) + } + + return net +} + +// GetAverageGas returns the average gas of the block +// window. +func (s *State) GetAverageGas(maxBlockGas uint64) math.LegacyDec { + var total uint64 + for _, gas := range s.Window { + total += gas + } + + sum := math.LegacyNewDecFromInt(math.NewIntFromUint64(total)) + + multiple := math.LegacyNewDecFromInt(math.NewIntFromUint64(uint64(len(s.Window)))) + divisor := math.LegacyNewDecFromInt(math.NewIntFromUint64(maxBlockGas)).Mul(multiple) + + return sum.Quo(divisor) +} + +// ValidateBasic performs basic validation on the state. +func (s *State) ValidateBasic() error { + if s.Window == nil { + return fmt.Errorf("block gas window cannot be nil or empty") + } + + if s.BaseGasPrice.IsNil() || s.BaseGasPrice.LTE(math.LegacyZeroDec()) { + return fmt.Errorf("base gas price must be positive") + } + + if s.LearningRate.IsNil() || s.LearningRate.LTE(math.LegacyZeroDec()) { + return fmt.Errorf("learning rate must be positive") + } + + return nil +} + +func GetTargetBlockGas(maxBlockGas uint64, params Params) uint64 { + targetBlockUtilization := params.TargetBlockUtilization + return uint64(math.LegacyNewDecFromInt(math.NewIntFromUint64(maxBlockGas)).Mul(targetBlockUtilization).TruncateInt().Int64()) +} diff --git a/x/dynamicfee/types/state_fuzz_test.go b/x/dynamicfee/types/state_fuzz_test.go new file mode 100644 index 000000000000..c3a7df3a35d9 --- /dev/null +++ b/x/dynamicfee/types/state_fuzz_test.go @@ -0,0 +1,108 @@ +package types_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +func FuzzDefaultDynamicfee(f *testing.F) { + testCases := []uint64{ + 0, + 1_000, + 10_000, + 100_000, + 1_000_000, + 10_000_000, + 100_000_000, + } + + for _, tc := range testCases { + f.Add(tc) + } + + defaultLR := math.LegacyMustNewDecFromStr("0.125") + + // Default dynamicfee. + f.Fuzz(func(t *testing.T, blockGasUsed uint64) { + state := types.DefaultState() + params := types.DefaultParams() + + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("100") + state.BaseGasPrice = math.LegacyMustNewDecFromStr("200") + err := state.Update(blockGasUsed, testutil.MaxBlockGas) + + if blockGasUsed > testutil.MaxBlockGas { + require.ErrorIs(t, err, types.ErrMaxGasExceeded) + return + } + + require.NoError(t, err) + require.Equal(t, blockGasUsed, state.Window[state.Index]) + + // Ensure the learning rate is always the default learning rate. + lr := state.UpdateLearningRate( + params, + testutil.MaxBlockGas, + ) + require.Equal(t, defaultLR, lr) + + oldFee := state.BaseGasPrice + newFee := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + if blockGasUsed > types.GetTargetBlockGas(testutil.MaxBlockGas, params) { + require.True(t, newFee.GT(oldFee)) + } else { + require.True(t, newFee.LT(oldFee)) + } + }) +} + +func FuzzAIMDDynamicfee(f *testing.F) { + testCases := []uint64{ + 0, + 1_000, + 10_000, + 100_000, + 1_000_000, + 10_000_000, + 100_000_000, + } + + for _, tc := range testCases { + f.Add(tc) + } + + // Dynamic fee pricing with adjustable learning rate. + f.Fuzz(func(t *testing.T, blockGasUsed uint64) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("100") + state.BaseGasPrice = math.LegacyMustNewDecFromStr("200") + state.Window = make([]uint64, 1) + err := state.Update(blockGasUsed, testutil.MaxBlockGas) + + if blockGasUsed > testutil.MaxBlockGas { + require.ErrorIs(t, err, types.ErrMaxGasExceeded) + return + } + + require.NoError(t, err) + require.Equal(t, blockGasUsed, state.Window[state.Index]) + + oldFee := state.BaseGasPrice + newFee := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + if blockGasUsed > types.GetTargetBlockGas(testutil.MaxBlockGas, params) { + require.True(t, newFee.GT(oldFee)) + } else { + require.True(t, newFee.LT(oldFee)) + } + }) +} diff --git a/x/dynamicfee/types/state_test.go b/x/dynamicfee/types/state_test.go new file mode 100644 index 000000000000..9de8b99be5f3 --- /dev/null +++ b/x/dynamicfee/types/state_test.go @@ -0,0 +1,773 @@ +package types_test + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "cosmossdk.io/log" + "cosmossdk.io/math" + + "github.com/cosmos/cosmos-sdk/x/dynamicfee/testutil" + "github.com/cosmos/cosmos-sdk/x/dynamicfee/types" +) + +var OneHundred = math.LegacyMustNewDecFromStr("100") + +func TestState_Update(t *testing.T) { + t.Run("can add to window", func(t *testing.T) { + state := types.DefaultState() + + err := state.Update(100, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(100), state.Window[0]) + }) + + t.Run("can add several txs to window", func(t *testing.T) { + state := types.DefaultState() + + err := state.Update(100, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(100), state.Window[0]) + + err = state.Update(200, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(300), state.Window[0]) + }) + + t.Run("errors when it exceeds max block gas", func(t *testing.T) { + state := types.DefaultState() + + err := state.Update(testutil.MaxBlockGas+1, testutil.MaxBlockGas) + require.Error(t, err) + }) + + t.Run("can update with several blocks in default eip-1559", func(t *testing.T) { + state := types.DefaultState() + + err := state.Update(100, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(100), state.Window[0]) + + state.IncrementHeight() + + err = state.Update(200, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(200), state.Window[0]) + + err = state.Update(300, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(500), state.Window[0]) + }) + + t.Run("can update with several blocks in default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + + err := state.Update(100, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(100), state.Window[0]) + + state.IncrementHeight() + + err = state.Update(200, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(200), state.Window[1]) + + state.IncrementHeight() + + err = state.Update(300, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(300), state.Window[2]) + + state.IncrementHeight() + + err = state.Update(400, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(400), state.Window[3]) + }) + + t.Run("correctly wraps around with aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + state.Window = make([]uint64, 3) + + err := state.Update(100, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(100), state.Window[0]) + + state.IncrementHeight() + + err = state.Update(200, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(200), state.Window[1]) + + state.IncrementHeight() + + err = state.Update(300, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(300), state.Window[2]) + + state.IncrementHeight() + require.Equal(t, uint64(0), state.Window[0]) + + err = state.Update(400, testutil.MaxBlockGas) + require.NoError(t, err) + require.Equal(t, uint64(400), state.Window[0]) + require.Equal(t, uint64(200), state.Window[1]) + require.Equal(t, uint64(300), state.Window[2]) + }) +} + +func TestState_UpdateBaseGasPrice(t *testing.T) { + t.Run("empty block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + state.BaseGasPrice = math.LegacyMustNewDecFromStr("1000") + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("125") + + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := math.LegacyMustNewDecFromStr("875") + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("target block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + state.BaseGasPrice = math.LegacyMustNewDecFromStr("1000") + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("125") + state.Window[0] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := math.LegacyMustNewDecFromStr("1000") + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("full block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + state.BaseGasPrice = math.LegacyMustNewDecFromStr("1000") + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("125") + state.Window[0] = testutil.MaxBlockGas + + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := math.LegacyMustNewDecFromStr("1125") + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("empty block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + state.BaseGasPrice = math.LegacyMustNewDecFromStr("1000") + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("125") + state.LearningRate = math.LegacyMustNewDecFromStr("0.125") + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := math.LegacyMustNewDecFromStr("850") + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("target block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + state.BaseGasPrice = math.LegacyMustNewDecFromStr("1000") + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("125") + state.LearningRate = math.LegacyMustNewDecFromStr("0.125") + for i := 0; i < len(state.Window); i++ { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := math.LegacyMustNewDecFromStr("1000") + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("full blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + state.BaseGasPrice = math.LegacyMustNewDecFromStr("1000") + params.MinBaseGasPrice = math.LegacyMustNewDecFromStr("125") + state.LearningRate = math.LegacyMustNewDecFromStr("0.125") + for i := 0; i < len(state.Window); i++ { + state.Window[i] = testutil.MaxBlockGas + } + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := math.LegacyMustNewDecFromStr("1150") + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("never goes below min base gas price with default eip1599", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + // Empty block + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := params.MinBaseGasPrice + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("never goes below min base gas price with default aimd eip1599", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + // Empty blocks + newBaseGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + require.Equal(t, newBaseGasPrice, state.BaseGasPrice) + expectedBaseGasPrice := params.MinBaseGasPrice + require.Equal(t, expectedBaseGasPrice, newBaseGasPrice) + }) + + t.Run("half target block gas with aimd eip1559", func(t *testing.T) { + state := types.DefaultAIMDState() + state.Window = make([]uint64, 1) + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyNewDec(10)) + prevGasPrice := state.BaseGasPrice + + params := types.DefaultAIMDParams() + params.Window = 1 + + // 1/4th of the window is full. + state.Window[0] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) / 2 + + prevLR := state.LearningRate + lr := state.UpdateLearningRate(params, testutil.MaxBlockGas) + newGasPrice := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + expectedLR := prevLR.Add(params.Alpha) + expectedGas := math.LegacyMustNewDecFromStr("-0.5") + expectedLRAdjustment := (expectedLR.Mul(expectedGas)).Add(math.LegacyOneDec()) + + expectedGasPrice := prevGasPrice.Mul(expectedLRAdjustment) + + require.Equal(t, expectedLR, lr) + require.Equal(t, expectedGasPrice, newGasPrice) + }) + + t.Run("3/4 max block gas with aimd eip1559", func(t *testing.T) { + state := types.DefaultAIMDState() + state.Window = make([]uint64, 1) + state.BaseGasPrice = state.BaseGasPrice.Mul(math.LegacyMustNewDecFromStr("10")) + prevBGS := state.BaseGasPrice + + params := types.DefaultAIMDParams() + params.Window = 1 + + // 1/4th of the window is full. + state.Window[0] = testutil.MaxBlockGas / 4 * 3 + + prevLR := state.LearningRate + lr := state.UpdateLearningRate(params, testutil.MaxBlockGas) + bgs := state.UpdateBaseGasPrice(log.NewNopLogger(), params, testutil.MaxBlockGas) + + expectedGas := math.LegacyMustNewDecFromStr("0.5") + expectedLR := prevLR.Add(params.Alpha) + expectedLRAdjustment := (expectedLR.Mul(expectedGas)).Add(math.LegacyOneDec()) + + expectedGasPrice := prevBGS.Mul(expectedLRAdjustment) + require.Equal(t, expectedLR, lr) + require.Equal(t, expectedGasPrice, bgs) + }) +} + +func TestState_UpdateLearningRate(t *testing.T) { + t.Run("empty block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := math.LegacyMustNewDecFromStr("0.125") + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("target block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + state.Window[0] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + expectedLearningRate := math.LegacyMustNewDecFromStr("0.125") + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("full block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + state.Window[0] = testutil.MaxBlockGas + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := math.LegacyMustNewDecFromStr("0.125") + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("between 0 and target with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + state.Window[0] = 50000 + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := math.LegacyMustNewDecFromStr("0.125") + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("between target and max with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + state.Window[0] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + 50000 + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := math.LegacyMustNewDecFromStr("0.125") + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("random value with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + randomValue := rand.Int63n(1000000000) + state.Window[0] = uint64(randomValue) + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := math.LegacyMustNewDecFromStr("0.125") + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("empty block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := params.MinLearningRate.Add(params.Alpha) + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("target block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + defaultLR := math.LegacyMustNewDecFromStr("0.125") + state.LearningRate = defaultLR + params := types.DefaultAIMDParams() + for i := 0; i < len(state.Window); i++ { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := defaultLR.Mul(params.Beta) + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("full blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + defaultLR := math.LegacyMustNewDecFromStr("0.125") + state.LearningRate = defaultLR + params := types.DefaultAIMDParams() + for i := 0; i < len(state.Window); i++ { + state.Window[i] = testutil.MaxBlockGas + } + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := defaultLR.Add(params.Alpha) + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("varying blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + defaultLR := math.LegacyMustNewDecFromStr("0.125") + state.LearningRate = defaultLR + params := types.DefaultAIMDParams() + for i := 0; i < len(state.Window); i++ { + if i%2 == 0 { + state.Window[i] = testutil.MaxBlockGas + } else { + state.Window[i] = 0 + } + } + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := defaultLR.Mul(params.Beta) + require.Equal(t, expectedLearningRate, state.LearningRate) + }) + + t.Run("exceeds threshold with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + defaultLR := math.LegacyMustNewDecFromStr("0.125") + state.LearningRate = defaultLR + params := types.DefaultAIMDParams() + for i := 0; i < len(state.Window); i++ { + if i%2 == 0 { + state.Window[i] = testutil.MaxBlockGas + } else { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + 1 + } + } + + state.UpdateLearningRate(params, testutil.MaxBlockGas) + + expectedLearningRate := defaultLR.Add(params.Alpha) + require.Equal(t, expectedLearningRate, state.LearningRate) + }) +} + +func TestState_GetNetGas(t *testing.T) { + t.Run("empty block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + expectedGas := math.NewInt(0).Sub(math.NewIntFromUint64(types.GetTargetBlockGas(testutil.MaxBlockGas, params))) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("target block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + state.Window[0] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + expectedGas := math.NewInt(0) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("full block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + state.Window[0] = testutil.MaxBlockGas + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + expectedGas := math.NewIntFromUint64(testutil.MaxBlockGas - types.GetTargetBlockGas(testutil.MaxBlockGas, params)) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("empty block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + + multiple := math.NewIntFromUint64(uint64(len(state.Window))) + expectedGas := math.NewInt(0).Sub(math.NewIntFromUint64(types.GetTargetBlockGas(testutil.MaxBlockGas, params))).Mul(multiple) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("full blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + for i := 0; i < len(state.Window); i++ { + state.Window[i] = testutil.MaxBlockGas + } + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + + multiple := math.NewIntFromUint64(uint64(len(state.Window))) + expectedGas := math.NewIntFromUint64(testutil.MaxBlockGas).Sub(math.NewIntFromUint64(types.GetTargetBlockGas(testutil.MaxBlockGas, params))).Mul(multiple) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("varying blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + for i := 0; i < len(state.Window); i++ { + if i%2 == 0 { + state.Window[i] = testutil.MaxBlockGas + } else { + state.Window[i] = 0 + } + } + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + expectedGas := math.ZeroInt() + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("exceeds target rate with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + for i := 0; i < len(state.Window); i++ { + if i%2 == 0 { + state.Window[i] = testutil.MaxBlockGas + } else { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + } + + netGas := state.GetNetGas(testutil.MaxBlockGas, params) + first := math.NewIntFromUint64(testutil.MaxBlockGas).Mul(math.NewIntFromUint64(params.Window / 2)) + second := math.NewIntFromUint64(types.GetTargetBlockGas(testutil.MaxBlockGas, params)).Mul(math.NewIntFromUint64(params.Window / 2)) + expectedGas := first.Add(second).Sub(math.NewIntFromUint64(types.GetTargetBlockGas(testutil.MaxBlockGas, params)).Mul(math.NewIntFromUint64(params.Window))) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("state with 4 entries in window with different updates", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + state.Window = make([]uint64, 4) + + maxBlockGas := uint64(200) + + state.Window[0] = 100 + state.Window[1] = 200 + state.Window[2] = 0 + state.Window[3] = 50 + + netGas := state.GetNetGas(maxBlockGas, params) + expectedGas := math.NewIntFromUint64(50).Mul(math.NewInt(-1)) + require.True(t, expectedGas.Equal(netGas)) + }) + + t.Run("state with 4 entries in window with monotonically increasing updates", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + state.Window = make([]uint64, 4) + + maxBlockGas := uint64(200) + + state.Window[0] = 0 + state.Window[1] = 25 + state.Window[2] = 50 + state.Window[3] = 75 + + netGas := state.GetNetGas(maxBlockGas, params) + expectedGas := math.NewIntFromUint64(250).Mul(math.NewInt(-1)) + require.True(t, expectedGas.Equal(netGas)) + }) +} + +func TestState_GetAverageGas(t *testing.T) { + t.Run("empty block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyZeroDec() + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("target block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + params := types.DefaultParams() + + state.Window[0] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("0.5") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("full block with default eip-1559", func(t *testing.T) { + state := types.DefaultState() + + state.Window[0] = testutil.MaxBlockGas + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("1.0") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("empty block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyZeroDec() + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("target block with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + for i := 0; i < len(state.Window); i++ { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("0.5") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("full blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + + for i := 0; i < len(state.Window); i++ { + state.Window[i] = testutil.MaxBlockGas + } + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("1.0") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("varying blocks with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + + for i := 0; i < len(state.Window); i++ { + if i%2 == 0 { + state.Window[i] = testutil.MaxBlockGas + } else { + state.Window[i] = 0 + } + } + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("0.5") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("exceeds target rate with default aimd eip-1559", func(t *testing.T) { + state := types.DefaultAIMDState() + params := types.DefaultAIMDParams() + + for i := 0; i < len(state.Window); i++ { + if i%2 == 0 { + state.Window[i] = testutil.MaxBlockGas + } else { + state.Window[i] = types.GetTargetBlockGas(testutil.MaxBlockGas, params) + } + } + + avgGas := state.GetAverageGas(testutil.MaxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("0.75") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("state with 4 entries in window with different updates", func(t *testing.T) { + state := types.DefaultAIMDState() + state.Window = make([]uint64, 4) + + maxBlockGas := uint64(200) + + state.Window[0] = 100 + state.Window[1] = 200 + state.Window[2] = 0 + state.Window[3] = 50 + + avgGas := state.GetAverageGas(maxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("0.4375") + require.True(t, expectedGas.Equal(avgGas)) + }) + + t.Run("state with 4 entries in window with monotonically increasing updates", func(t *testing.T) { + state := types.DefaultAIMDState() + state.Window = make([]uint64, 4) + + params := types.DefaultAIMDParams() + params.Window = 4 + maxBlockGas := uint64(200) + + state.Window[0] = 0 + state.Window[1] = 25 + state.Window[2] = 50 + state.Window[3] = 75 + + avgGas := state.GetAverageGas(maxBlockGas) + expectedGas := math.LegacyMustNewDecFromStr("0.1875") + require.True(t, expectedGas.Equal(avgGas)) + }) +} + +func TestState_ValidateBasic(t *testing.T) { + testCases := []struct { + name string + state types.State + expectErr bool + }{ + { + name: "default base EIP-1559 state", + state: types.DefaultState(), + expectErr: false, + }, + { + name: "default AIMD EIP-1559 state", + state: types.DefaultAIMDState(), + expectErr: false, + }, + { + name: "invalid window", + state: types.State{}, + expectErr: true, + }, + { + name: "invalid negative base gas price", + state: types.State{ + Window: make([]uint64, 1), + BaseGasPrice: math.LegacyMustNewDecFromStr("-1"), + }, + expectErr: true, + }, + { + name: "invalid learning rate", + state: types.State{ + Window: make([]uint64, 1), + BaseGasPrice: math.LegacyMustNewDecFromStr("1"), + LearningRate: math.LegacyMustNewDecFromStr("-1.0"), + }, + expectErr: true, + }, + { + name: "valid other state", + state: types.State{ + Window: make([]uint64, 1), + BaseGasPrice: math.LegacyMustNewDecFromStr("1"), + LearningRate: math.LegacyMustNewDecFromStr("0.5"), + }, + expectErr: false, + }, + { + name: "invalid zero base gas price", + state: types.State{ + Window: make([]uint64, 1), + BaseGasPrice: math.LegacyZeroDec(), + LearningRate: math.LegacyMustNewDecFromStr("0.5"), + }, + expectErr: true, + }, + { + name: "invalid zero learning rate", + state: types.State{ + Window: make([]uint64, 1), + BaseGasPrice: math.LegacyMustNewDecFromStr("1"), + LearningRate: math.LegacyZeroDec(), + }, + expectErr: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.state.ValidateBasic() + if tc.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/dynamicfee/types/tx.pb.go b/x/dynamicfee/types/tx.pb.go new file mode 100644 index 000000000000..9ab446c07416 --- /dev/null +++ b/x/dynamicfee/types/tx.pb.go @@ -0,0 +1,597 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: cosmos/dynamicfee/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + _ "github.com/cosmos/cosmos-sdk/types/msgservice" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" + _ "github.com/cosmos/gogoproto/gogoproto" + grpc1 "github.com/cosmos/gogoproto/grpc" + proto "github.com/cosmos/gogoproto/proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + 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 + +// MsgUpdateParams defines the sdk.Msg/UpdateParams request type. It contains +// the new parameters for the dynamicfee module. +type MsgUpdateParams struct { + // Authority defines the authority that is updating the dynamicfee module + // parameters. + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // Params defines the new parameters for the dynamicfee module. + Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"` +} + +func (m *MsgUpdateParams) Reset() { *m = MsgUpdateParams{} } +func (m *MsgUpdateParams) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParams) ProtoMessage() {} +func (*MsgUpdateParams) Descriptor() ([]byte, []int) { + return fileDescriptor_9898bd7eda52fac0, []int{0} +} +func (m *MsgUpdateParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParams.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 *MsgUpdateParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParams.Merge(m, src) +} +func (m *MsgUpdateParams) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParams) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParams.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParams proto.InternalMessageInfo + +func (m *MsgUpdateParams) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpdateParams) GetParams() Params { + if m != nil { + return m.Params + } + return Params{} +} + +// MsgUpdateParamsResponse defines the response structure for executing a +// MsgUpdateParams message. +type MsgUpdateParamsResponse struct { +} + +func (m *MsgUpdateParamsResponse) Reset() { *m = MsgUpdateParamsResponse{} } +func (m *MsgUpdateParamsResponse) String() string { return proto.CompactTextString(m) } +func (*MsgUpdateParamsResponse) ProtoMessage() {} +func (*MsgUpdateParamsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9898bd7eda52fac0, []int{1} +} +func (m *MsgUpdateParamsResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpdateParamsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpdateParamsResponse.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 *MsgUpdateParamsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpdateParamsResponse.Merge(m, src) +} +func (m *MsgUpdateParamsResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpdateParamsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpdateParamsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpdateParamsResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgUpdateParams)(nil), "cosmos.dynamicfee.v1.MsgUpdateParams") + proto.RegisterType((*MsgUpdateParamsResponse)(nil), "cosmos.dynamicfee.v1.MsgUpdateParamsResponse") +} + +func init() { proto.RegisterFile("cosmos/dynamicfee/v1/tx.proto", fileDescriptor_9898bd7eda52fac0) } + +var fileDescriptor_9898bd7eda52fac0 = []byte{ + // 342 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4d, 0xce, 0x2f, 0xce, + 0xcd, 0x2f, 0xd6, 0x4f, 0xa9, 0xcc, 0x4b, 0xcc, 0xcd, 0x4c, 0x4e, 0x4b, 0x4d, 0xd5, 0x2f, 0x33, + 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x81, 0x48, 0xeb, 0x21, 0xa4, + 0xf5, 0xca, 0x0c, 0xa5, 0x14, 0xb1, 0x6a, 0x2a, 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0x86, 0x68, 0x94, + 0x92, 0x84, 0x28, 0x89, 0x07, 0xf3, 0xf4, 0xa1, 0xa6, 0x40, 0xa4, 0xc4, 0xa1, 0xba, 0x73, 0x8b, + 0xd3, 0x41, 0xda, 0x72, 0x8b, 0xd3, 0xa1, 0x12, 0x22, 0xe9, 0xf9, 0xe9, 0xf9, 0x10, 0x0d, 0x20, + 0x16, 0x54, 0x54, 0x30, 0x31, 0x37, 0x33, 0x2f, 0x5f, 0x1f, 0x4c, 0x42, 0x84, 0x94, 0x0e, 0x30, + 0x72, 0xf1, 0xfb, 0x16, 0xa7, 0x87, 0x16, 0xa4, 0x24, 0x96, 0xa4, 0x06, 0x80, 0xad, 0x15, 0x32, + 0xe3, 0xe2, 0x4c, 0x2c, 0x2d, 0xc9, 0xc8, 0x2f, 0xca, 0x2c, 0xa9, 0x94, 0x60, 0x54, 0x60, 0xd4, + 0xe0, 0x74, 0x92, 0xb8, 0xb4, 0x45, 0x17, 0xe6, 0x01, 0xc7, 0x94, 0x94, 0xa2, 0xd4, 0xe2, 0xe2, + 0xe0, 0x92, 0xa2, 0xcc, 0xbc, 0xf4, 0x20, 0x84, 0x52, 0x21, 0x2b, 0x2e, 0x36, 0x88, 0xc3, 0x25, + 0x98, 0x14, 0x18, 0x35, 0xb8, 0x8d, 0x64, 0xf4, 0xb0, 0x79, 0x59, 0x0f, 0x62, 0x8b, 0x13, 0xcb, + 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, 0x1d, 0x56, 0x96, 0x4d, 0xcf, 0x37, 0x68, 0x21, 0xcc, 0xea, + 0x7a, 0xbe, 0x41, 0x4b, 0x0d, 0xea, 0xb9, 0x0a, 0xb4, 0xc0, 0x41, 0x73, 0xae, 0x92, 0x24, 0x97, + 0x38, 0x9a, 0x50, 0x50, 0x6a, 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x51, 0x11, 0x17, 0xb3, 0x6f, + 0x71, 0xba, 0x50, 0x0a, 0x17, 0x0f, 0x8a, 0x07, 0x55, 0xb1, 0x3b, 0x0c, 0xcd, 0x14, 0x29, 0x5d, + 0xa2, 0x94, 0xc1, 0x2c, 0x93, 0x62, 0x6d, 0x78, 0xbe, 0x41, 0x8b, 0xd1, 0xc9, 0xf3, 0xc4, 0x23, + 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, + 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, 0xa2, 0xf4, 0xd3, 0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, + 0x92, 0xf3, 0x73, 0xa1, 0xd1, 0x08, 0xa5, 0x74, 0x8b, 0x53, 0xb2, 0x51, 0xbd, 0x59, 0x52, 0x59, + 0x90, 0x5a, 0x9c, 0xc4, 0x06, 0x8e, 0x23, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x06, 0x2d, + 0x04, 0x8b, 0x5a, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // UpdateParams defines a method for updating the dynamicfee module parameters. + UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) UpdateParams(ctx context.Context, in *MsgUpdateParams, opts ...grpc.CallOption) (*MsgUpdateParamsResponse, error) { + out := new(MsgUpdateParamsResponse) + err := c.cc.Invoke(ctx, "/cosmos.dynamicfee.v1.Msg/UpdateParams", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // UpdateParams defines a method for updating the dynamicfee module parameters. + UpdateParams(context.Context, *MsgUpdateParams) (*MsgUpdateParamsResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) UpdateParams(ctx context.Context, req *MsgUpdateParams) (*MsgUpdateParamsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateParams not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_UpdateParams_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpdateParams) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpdateParams(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/cosmos.dynamicfee.v1.Msg/UpdateParams", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpdateParams(ctx, req.(*MsgUpdateParams)) + } + return interceptor(ctx, in, info, handler) +} + +var Msg_serviceDesc = _Msg_serviceDesc +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "cosmos.dynamicfee.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "UpdateParams", + Handler: _Msg_UpdateParams_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "cosmos/dynamicfee/v1/tx.proto", +} + +func (m *MsgUpdateParams) 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 *MsgUpdateParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpdateParamsResponse) 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 *MsgUpdateParamsResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpdateParamsResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgUpdateParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = m.Params.Size() + n += 1 + l + sovTx(uint64(l)) + return n +} + +func (m *MsgUpdateParamsResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgUpdateParams) 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 ErrIntOverflowTx + } + 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: MsgUpdateParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Params", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Params.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpdateParamsResponse) 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 ErrIntOverflowTx + } + 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: MsgUpdateParamsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpdateParamsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(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, ErrIntOverflowTx + } + 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, ErrIntOverflowTx + } + 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, ErrIntOverflowTx + } + 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, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/staking/testutil/app_config.go b/x/staking/testutil/app_config.go index 4f9118e395ce..51498a8d5173 100644 --- a/x/staking/testutil/app_config.go +++ b/x/staking/testutil/app_config.go @@ -7,6 +7,7 @@ import ( _ "github.com/cosmos/cosmos-sdk/x/bank" // import as blank for app wiring _ "github.com/cosmos/cosmos-sdk/x/consensus" // import as blank for app wiring _ "github.com/cosmos/cosmos-sdk/x/distribution" // import as blank for app wiring + _ "github.com/cosmos/cosmos-sdk/x/dynamicfee" // import as blank for app wiring _ "github.com/cosmos/cosmos-sdk/x/genutil" // import as blank for app wiring _ "github.com/cosmos/cosmos-sdk/x/mint" // import as blank for app wiring _ "github.com/cosmos/cosmos-sdk/x/params" // import as blank for app wiring @@ -24,4 +25,5 @@ var AppConfig = configurator.NewAppConfig( configurator.GenutilModule(), configurator.MintModule(), configurator.DistributionModule(), + configurator.DynamicfeeModule(), )