Skip to content
Open
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
15 changes: 14 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ import (
"github.com/evmos/evmos/v12/x/ibc/transfer"
transferkeeper "github.com/evmos/evmos/v12/x/ibc/transfer/keeper"

v2 "github.com/evmos/evmos/v12/app/upgrades/v2"

// Force-load the tracer engines to trigger registration due to Go-Ethereum v1.10.15 changes
"github.com/ethereum/go-ethereum/core/vm"
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
Expand Down Expand Up @@ -1077,7 +1079,7 @@ func (app *Evmos) EvmPrecompiled() {

// distribution precompile
precompiled[precompilesdistribution.GetAddress()] = func(ctx sdk.Context) vm.PrecompiledContract {
return precompilesdistribution.NewPrecompiledContract(ctx, app.DistrKeeper)
return precompilesdistribution.NewPrecompiledContract(ctx, app.DistrKeeper, app.EvmKeeper)
}

// gov precompile
Expand Down Expand Up @@ -1110,6 +1112,14 @@ func (app *Evmos) EvmPrecompiled() {
}

func (app *Evmos) setupUpgradeHandlers() {
// v2.0.0 upgrade handler
app.UpgradeKeeper.SetUpgradeHandler(
v2.UpgradeName,
v2.CreateUpgradeHandler(
app.mm, app.configurator, app.EvmKeeper,
),
)

// When a planned update height is reached, the old binary will panic
// writing on disk the height and name of the update that triggered it
// This will read that value, and execute the preparations for the upgrade.
Expand All @@ -1125,6 +1135,9 @@ func (app *Evmos) setupUpgradeHandlers() {
var storeUpgrades *storetypes.StoreUpgrades

switch upgradeInfo.Name {
case v2.UpgradeName:
// v2.0.0: No store upgrades needed.
// Gas fee distribution statistics use new key prefixes in existing EVM store.
}

if storeUpgrades != nil {
Expand Down
44 changes: 44 additions & 0 deletions app/upgrades/v2/upgrades.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package v2

import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
evmkeeper "github.com/evmos/evmos/v12/x/evm/keeper"
)

const (
// UpgradeName is the shared upgrade plan name for mainnet
UpgradeName = "v2.0.0"
// UpgradeInfo defines the binaries that will be used for the upgrade
UpgradeInfo = `'{"binaries":{"darwin/arm64":"https://github.com/mud-chain/mud/releases/download/v2.0.0/mud_2.0.0_darwin_arm64.tar.gz","darwin/amd64":"https://github.com/mud-chain/mud/releases/download/v2.0.0/mud_2.0.0_darwin_amd64.tar.gz","linux/arm64":"https://github.com/mud-chain/mud/releases/download/v2.0.0/mud_2.0.0_linux_arm64.tar.gz","linux/amd64":"https://github.com/mud-chain/mud/releases/download/v2.0.0/mud_2.0.0_linux_amd64.tar.gz","windows/x86_64":"https://github.com/mud-chain/mud/releases/download/v2.0.0/mud_2.0.0_windows_x86_64.zip"}}'`
)

// CreateUpgradeHandler creates an SDK upgrade handler for v2.0.0
//
// This upgrade introduces gas fee distribution mechanism:
// - 40% of gas fees are burned (sent to zero address)
// - 40% distributed to validators
// - 20% sent to core team address
//
// The upgrade initializes gas fee statistics tracking for these distributions.
func CreateUpgradeHandler(
mm *module.Manager,
configurator module.Configurator,
evmKeeper *evmkeeper.Keeper,
) upgradetypes.UpgradeHandler {
return func(ctx sdk.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) {
logger := ctx.Logger().With("upgrade", UpgradeName)

// Initialize gas fee statistics
logger.Info("initializing gas fee distribution statistics")
evmKeeper.SetTotalBurnedFee(ctx, sdkmath.ZeroInt())
evmKeeper.SetTotalCoreTeamFee(ctx, sdkmath.ZeroInt())
evmKeeper.SetTotalValidatorFee(ctx, sdkmath.ZeroInt())

// Leave modules as-is to avoid running InitGenesis.
logger.Debug("running module migrations ...")
return mm.RunMigrations(ctx, configurator, vm)
}
}
19 changes: 19 additions & 0 deletions proto/ethermint/evm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ service Query {
rpc BaseFee(QueryBaseFeeRequest) returns (QueryBaseFeeResponse) {
option (google.api.http).get = "/evmos/evm/v1/base_fee";
}

// GasFeeStatistics queries the gas fee distribution statistics (total burned and project fees).
rpc GasFeeStatistics(QueryGasFeeStatisticsRequest) returns (QueryGasFeeStatisticsResponse) {
option (google.api.http).get = "/evmos/evm/v1/gas_fee_statistics";
}
}

// QueryAccountRequest is the request type for the Query/Account RPC method.
Expand Down Expand Up @@ -296,3 +301,17 @@ message QueryBaseFeeResponse {
// base_fee is the EIP1559 base fee
string base_fee = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int"];
}

// QueryGasFeeStatisticsRequest defines the request type for querying gas fee
// distribution statistics.
message QueryGasFeeStatisticsRequest {}

// QueryGasFeeStatisticsResponse returns the gas fee distribution statistics.
message QueryGasFeeStatisticsResponse {
// total_burned_fee is the total amount of gas fees that have been burned
string total_burned_fee = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
// total_core_team_fee is the total amount of gas fees sent to the core team address
string total_core_team_fee = 2 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
// total_validator_fee is the total amount of gas fees distributed to validators
string total_validator_fee = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
}
15 changes: 15 additions & 0 deletions solidity/contracts/distribution/IDistribution.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ struct DelegationDelegatorReward {
DecCoin[] rewards;
}

/**
* @dev GasFeeStatistics represents the gas fee distribution statistics
*/
struct GasFeeStatistics {
uint256 totalBurnedFee;
uint256 totalCoreTeamFee;
uint256 totalValidatorFee;
}


/**
* @dev Params defines the set of params for the distribution module.
Expand Down Expand Up @@ -147,6 +156,12 @@ interface IDistribution {
address delegatorAddress
) external view returns (address withdrawAddress);

/**
* @dev gasFeeStatistics queries the gas fee distribution statistics.
* Returns total burned fees, total core team fees, and total validator fees.
*/
function gasFeeStatistics() external view returns (GasFeeStatistics memory statistics);

/**
* @dev SetWithdrawAddress defines an Event emitted when a user change the withdraw address
*/
Expand Down
109 changes: 109 additions & 0 deletions x/evm/keeper/gas_distribution.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package keeper

import (
"math/big"

"github.com/ethereum/go-ethereum/core"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)

const (
// Gas fee distribution ratios (in percentage)
BurnRatio = 40 // 40% burn (send to zero address)
ValidatorRatio = 40 // 40% to validators (kept in FeeCollector)
CoreTeamRatio = 20 // 20% to core team

// Zero address for burning tokens
BurnAddress = "mud1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq4sx9vz"

// Core team address (hardcoded)
CoreTeamAddress = "mud1d58wfeq0re6awm7t3qynu6dz5cwk787phgl0f9"
)

// DistributeGasFee distributes the gas fee according to the configured ratios:
// - 40% sent to zero address (burn)
// - 40% to validators (kept in FeeCollector for distribution module)
// - 20% to core team address
func (k *Keeper) DistributeGasFee(ctx sdk.Context, msg core.Message, gasUsed uint64, denom string) error {
// Calculate the actual gas fee consumed
gasFee := new(big.Int).Mul(new(big.Int).SetUint64(gasUsed), msg.GasPrice())

// If gas fee is zero, nothing to distribute
if gasFee.Sign() <= 0 {
return nil
}

totalFee := sdkmath.NewIntFromBigInt(gasFee)

// Calculate distribution amounts
// burnAmount = totalFee * 40 / 100
burnAmount := totalFee.Mul(sdkmath.NewInt(BurnRatio)).Quo(sdkmath.NewInt(100))
// coreTeamAmount = totalFee * 20 / 100
coreTeamAmount := totalFee.Mul(sdkmath.NewInt(CoreTeamRatio)).Quo(sdkmath.NewInt(100))
// validatorAmount = totalFee - burnAmount - coreTeamAmount (remaining goes to validators)
validatorAmount := totalFee.Sub(burnAmount).Sub(coreTeamAmount)

// 1. Send 40% to zero address (burn)
if burnAmount.IsPositive() {
burnAddr, err := sdk.AccAddressFromBech32(BurnAddress)
if err != nil {
k.Logger(ctx).Error("invalid burn address", "address", BurnAddress, "error", err)
} else {
burnCoins := sdk.NewCoins(sdk.NewCoin(denom, burnAmount))
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, burnAddr, burnCoins); err != nil {
return errorsmod.Wrapf(err, "failed to send gas fee to burn address: %s", burnCoins.String())
}

// Update burn statistics
k.AddTotalBurnedFee(ctx, burnAmount)

k.Logger(ctx).Debug("sent gas fee to burn address", "amount", burnAmount.String(), "denom", denom, "address", BurnAddress)
}
}

// 2. Send 20% to core team address
if coreTeamAmount.IsPositive() {
coreTeamAddr, err := sdk.AccAddressFromBech32(CoreTeamAddress)
if err != nil {
// If core team address is invalid, log error but don't fail the transaction
// The core team amount will remain in FeeCollector
k.Logger(ctx).Error("invalid core team address", "address", CoreTeamAddress, "error", err)
} else {
coreTeamCoins := sdk.NewCoins(sdk.NewCoin(denom, coreTeamAmount))
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, coreTeamAddr, coreTeamCoins); err != nil {
return errorsmod.Wrapf(err, "failed to send gas fee to core team: %s", coreTeamCoins.String())
}

// Update core team fee statistics
k.AddTotalCoreTeamFee(ctx, coreTeamAmount)

k.Logger(ctx).Debug("sent gas fee to core team", "amount", coreTeamAmount.String(), "denom", denom, "address", CoreTeamAddress)
}
}

// 3. The remaining 40% stays in FeeCollector and will be distributed to validators
// by the distribution module at the end of each block
// Update validator fee statistics
if validatorAmount.IsPositive() {
k.AddTotalValidatorFee(ctx, validatorAmount)
}

// Emit event for gas fee distribution
ctx.EventManager().EmitEvent(
sdk.NewEvent(
"gas_fee_distribution",
sdk.NewAttribute("gas_used", sdkmath.NewIntFromUint64(gasUsed).String()),
sdk.NewAttribute("total_fee", totalFee.String()),
sdk.NewAttribute("burned", burnAmount.String()),
sdk.NewAttribute("core_team", coreTeamAmount.String()),
sdk.NewAttribute("validator", validatorAmount.String()),
sdk.NewAttribute("denom", denom),
),
)

return nil
}
Loading