Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: zero gas fee poc #29

Draft
wants to merge 1 commit into
base: basechain/develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions app/ante/handler_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package ante

import (
"math/big"

errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
txsigning "cosmossdk.io/x/tx/signing"
Expand Down Expand Up @@ -92,6 +94,35 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
baseFee := blockCfg.BaseFee
rules := blockCfg.Rules

isZeroGas := false
fromAddrs := make([]sdk.AccAddress, 0)
for _, m := range tx.GetMsgs() {
msgEthTx, ok := m.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", m, (*evmtypes.MsgEthereumTx)(nil))
}
msg := msgEthTx.AsMessage(nil)

fromAddrs = append(fromAddrs, msg.From.Bytes())

if msg.To != nil && len(msg.Data) >= 4 {
toAddr := msg.To.Bytes()
signature := msg.Data[:4]
if options.EvmKeeper.HasZeroGas(ctx, toAddr, signature) {
isZeroGas = true
}
}
}
if isZeroGas {
for _, fromAddr := range fromAddrs {
if options.AccountKeeper.GetAccount(ctx, fromAddr) == nil {
newAcc := options.AccountKeeper.NewAccountWithAddress(ctx, fromAddr)
options.AccountKeeper.SetAccount(ctx, newAcc)
}
}
baseFee = big.NewInt(0)
}

// all transactions must implement FeeTx
_, ok := tx.(sdk.FeeTx)
if !ok {
Expand Down
1 change: 1 addition & 0 deletions app/ante/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type EVMKeeper interface {
ChainID() *big.Int
EVMBlockConfig(sdk.Context, *big.Int) (*evmkeeper.EVMBlockConfig, error)

HasZeroGas(ctx sdk.Context, addr []byte, sig []byte) bool
DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from common.Address) error
}

Expand Down
3 changes: 3 additions & 0 deletions proto/ethermint/evm/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";
package ethermint.evm.v1;

import "ethermint/evm/v1/params.proto";
import "ethermint/evm/v1/zero_gas.proto";
import "ethermint/evm/v1/state.proto";
import "gogoproto/gogo.proto";

Expand All @@ -13,6 +14,8 @@ message GenesisState {
repeated GenesisAccount accounts = 1 [(gogoproto.nullable) = false];
// params defines all the parameters of the module.
Params params = 2 [(gogoproto.nullable) = false];
// zero_gas defines the zero gas config for the module.
repeated ZeroGas zero_gas = 3 [(gogoproto.nullable) = false];
}

// GenesisAccount defines an account to be initialized in the genesis state.
Expand Down
32 changes: 32 additions & 0 deletions proto/ethermint/evm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "ethermint/evm/v1/tx.proto";
import "ethermint/evm/v1/log.proto";
import "ethermint/evm/v1/params.proto";
import "ethermint/evm/v1/trace_config.proto";
import "ethermint/evm/v1/zero_gas.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "google/protobuf/timestamp.proto";
Expand Down Expand Up @@ -81,6 +82,16 @@ service Query {
rpc BaseFee(QueryBaseFeeRequest) returns (QueryBaseFeeResponse) {
option (google.api.http).get = "/ethermint/evm/v1/base_fee";
}

// ZeroGas queries the zero gas config for a given contract address and signature
rpc ZeroGas(QueryZeroGasRequest) returns (QueryZeroGasResponse) {
option (google.api.http).get = "/ethermint/evm/v1/zero_gas";
}

// AllZeroGas queries all the zero gas
rpc AllZeroGas(QueryAllZeroGasRequest) returns (QueryAllZeroGasResponse) {
option (google.api.http).get = "/ethermint/evm/v1/all_zero_gas";
}
}

// QueryAccountRequest is the request type for the Query/Account RPC method.
Expand Down Expand Up @@ -336,3 +347,24 @@ message QueryBaseFeeResponse {
// base_fee is the EIP1559 base fee
string base_fee = 1 [(gogoproto.customtype) = "cosmossdk.io/math.Int"];
}

// QueryZeroGasRequest defines ZeroGas request
message QueryZeroGasRequest {
// contract_address is the contract address to query the zero gas for
string contract_address = 1;
}

// QueryZeroGasResponse defines ZeroGas response
message QueryZeroGasResponse {
// items is the zero gas items
repeated string signatures = 1;
}

// QueryAllZeroGasRequest defines AllZeroGas request
message QueryAllZeroGasRequest {}

// QueryAllZeroGasResponse defines AllZeroGas response
message QueryAllZeroGasResponse {
// items is the zero gas items
repeated ZeroGas items = 1 [ (gogoproto.nullable) = false ];
}
23 changes: 23 additions & 0 deletions proto/ethermint/evm/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import "cosmos_proto/cosmos.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";
import "google/protobuf/any.proto";
import "amino/amino.proto";
import "ethermint/evm/v1/access_tuple.proto";
import "ethermint/evm/v1/log.proto";
import "ethermint/evm/v1/params.proto";
import "ethermint/evm/v1/zero_gas.proto";

option go_package = "github.com/evmos/ethermint/x/evm/types";

Expand All @@ -22,6 +24,8 @@ service Msg {
// UpdateParams defined a governance operation for updating the x/evm module parameters.
// The authority is hard-coded to the Cosmos SDK x/gov module account
rpc UpdateParams(MsgUpdateParams) returns (MsgUpdateParamsResponse);
// UpdateZeroGas defines a method for updating the zero gas config
rpc UpdateZeroGas(MsgUpdateZeroGas) returns (MsgUpdateZeroGasResponse);
}

// MsgEthereumTx encapsulates an Ethereum transaction as an SDK message.
Expand Down Expand Up @@ -187,3 +191,22 @@ message MsgUpdateParams {
// MsgUpdateParamsResponse defines the response structure for executing a
// MsgUpdateParams message.
message MsgUpdateParamsResponse {}

// MsgUpdateZeroGas defines a Msg for updating the x/evm module zero gas config.
message MsgUpdateZeroGas {
option (cosmos.msg.v1.signer) = "authority";

// authority is the address that controls the module (defaults to x/gov unless
// overwritten).
string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ];
option (amino.name) = "basechain/MsgUpdateZeroGasConfig";

option (gogoproto.equal) = false;

// metadata defines the zero gas to update.
UpdateZeroGasMetadata metadata = 2 [(gogoproto.nullable) = false];
}

// MsgUpdateZeroGasResponse defines the response structure for executing a
// MsgUpdateZeroGas message.
message MsgUpdateZeroGasResponse {}
23 changes: 23 additions & 0 deletions proto/ethermint/evm/v1/zero_gas.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";
package ethermint.evm.v1;

import "gogoproto/gogo.proto";

option go_package = "github.com/evmos/ethermint/x/evm/types";

// ZeroGas defines the parameters for zero gas fee transactions
message ZeroGas {
// contract_address specifies the target contract address for zero gas fee transactions
string contract_address = 1 [(gogoproto.moretags) = "yaml:\"contract_address\""];
// signatures is a list of function signatures that will have zero gas fee
// when called on the specified contract_address
repeated string signatures = 2 [(gogoproto.moretags) = "yaml:\"signatures\""];
}

message UpdateZeroGasMetadata {
// zero gas items to be added
repeated ZeroGas add_items = 1;

// zero gas items to be removed
repeated ZeroGas remove_items = 2;
}
58 changes: 58 additions & 0 deletions rpc/backend/mocks/evm_query_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions x/evm/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ func GetQueryCmd() *cobra.Command {
GetStorageCmd(),
GetCodeCmd(),
GetParamsCmd(),
GetZeroGasCmd(),
GetAllZeroGasCmd(),
)
return cmd
}
Expand Down Expand Up @@ -147,3 +149,59 @@ func GetParamsCmd() *cobra.Command {
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

// GetZeroGasCmd queries the zero gas for a given contract address
func GetZeroGasCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "zero-gas ADDRESS",
Short: "Get the zero gas for a given contract address",
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)

res, err := queryClient.ZeroGas(cmd.Context(), &types.QueryZeroGasRequest{
ContractAddress: args[0],
})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}

// GetAllZeroGasCmd queries the all zero gas for a given contract address
func GetAllZeroGasCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "all-zero-gas",
Short: "Get the all zero gas",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

queryClient := types.NewQueryClient(clientCtx)

res, err := queryClient.AllZeroGas(cmd.Context(), &types.QueryAllZeroGasRequest{})
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}
20 changes: 20 additions & 0 deletions x/evm/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ func InitGenesis(
}
}

for _, config := range data.ZeroGas {
if !common.IsHexAddress(config.ContractAddress) {
panic(fmt.Sprintf("invalid contract address: %s", config.ContractAddress))
}

contractAddr := common.FromHex(config.ContractAddress)
for _, s := range config.Signatures {
sig := common.FromHex(s)
if len(sig) != 4 {
panic(fmt.Sprintf("invalid signature: %s", s))
}

err = k.SetZeroGas(ctx, contractAddr, sig)
if err != nil {
panic(fmt.Errorf("error setting zero gas config %s", err))
}
}
}

return []abci.ValidatorUpdate{}
}

Expand Down Expand Up @@ -112,5 +131,6 @@ func ExportGenesis(ctx sdk.Context, k *keeper.Keeper, ak types.AccountKeeper) *t
return &types.GenesisState{
Accounts: ethGenAccounts,
Params: k.GetParams(ctx),
ZeroGas: k.GetAllZeroGas(ctx),
}
}
20 changes: 20 additions & 0 deletions x/evm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,23 @@
}
return big.NewInt(chainID), nil
}

// ZeroGas implements the Query/ZeroGas gRPC method
func (k Keeper) ZeroGas(c context.Context, req *types.QueryZeroGasRequest) (*types.QueryZeroGasResponse, error) {
ctx := sdk.UnwrapSDKContext(c)

if !common.IsHexAddress(req.ContractAddress) {
return nil, status.Errorf(codes.Internal, "invalid contract address: %s", req.ContractAddress)
}

contractAddr := common.FromHex(req.ContractAddress)
sigs := k.GetAllZeroGasSigsByAddr(ctx, contractAddr)
return &types.QueryZeroGasResponse{Signatures: sigs}, nil
}

// AllZeroGas implements the Query/AllZeroGas gRPC method
func (k Keeper) AllZeroGas(c context.Context, req *types.QueryAllZeroGasRequest) (*types.QueryAllZeroGasResponse, error) {

Check warning on line 785 in x/evm/keeper/grpc_query.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

unused-parameter: parameter 'req' seems to be unused, consider removing or renaming it as _ (revive)
ctx := sdk.UnwrapSDKContext(c)
items := k.GetAllZeroGas(ctx)
return &types.QueryAllZeroGasResponse{Items: items}, nil
}
Loading
Loading