Skip to content

Commit

Permalink
feat: zero gas fee
Browse files Browse the repository at this point in the history
  • Loading branch information
dudong2 committed Dec 5, 2024
1 parent 06b761b commit 8c98958
Show file tree
Hide file tree
Showing 19 changed files with 3,222 additions and 194 deletions.
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 @@ func getChainID(ctx sdk.Context, chainID int64) (*big.Int, error) {
}
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

0 comments on commit 8c98958

Please sign in to comment.