diff --git a/evmd/app.go b/evmd/app.go index fe494f779..687b3064a 100644 --- a/evmd/app.go +++ b/evmd/app.go @@ -32,6 +32,7 @@ import ( "github.com/cosmos/evm/x/feemarket" feemarketkeeper "github.com/cosmos/evm/x/feemarket/keeper" feemarkettypes "github.com/cosmos/evm/x/feemarket/types" + // NOTE: override ICS20 keeper to support IBC transfers of ERC20 tokens "github.com/cosmos/evm/x/ibc/transfer" transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper" diff --git a/precompiles/bank/integration_test.go b/precompiles/bank/integration_test.go index 9e9511a41..df8d9bf0e 100644 --- a/precompiles/bank/integration_test.go +++ b/precompiles/bank/integration_test.go @@ -111,8 +111,8 @@ var _ = Describe("Bank Extension -", func() { contractData ContractData passCheck testutil.LogCheckArgs - cosmosEVMTotalSupply, _ = new(big.Int).SetString("200003000000000000000000", 10) - xmplTotalSupply, _ = new(big.Int).SetString("200000000000000000000000", 10) + cosmosEVMTotalSupply *big.Int + xmplTotalSupply *big.Int ) BeforeEach(func() { @@ -146,6 +146,12 @@ var _ = Describe("Bank Extension -", func() { err = is.network.NextBlock() Expect(err).ToNot(HaveOccurred(), "failed to advance block") + + // Get actual total supply after contract deployment + totSupplRes, err := is.grpcHandler.GetTotalSupply() + Expect(err).ToNot(HaveOccurred(), "failed to get total supply") + cosmosEVMTotalSupply = totSupplRes.Supply.AmountOf(is.bondDenom).BigInt() + xmplTotalSupply = totSupplRes.Supply.AmountOf(is.tokenDenom).BigInt() }) Context("Direct precompile queries", func() { diff --git a/rpc/backend/tracing.go b/rpc/backend/tracing.go index 721bbde3a..f13222845 100644 --- a/rpc/backend/tracing.go +++ b/rpc/backend/tracing.go @@ -127,6 +127,7 @@ func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfi // add predecessor messages in current cosmos tx index := int(transaction.MsgIndex) // #nosec G115 + for i := 0; i < index; i++ { msg := tx.GetMsgs()[i] // Check if it's a normal Ethereum tx diff --git a/rpc/backend/tracing_test.go b/rpc/backend/tracing_test.go index 8bc1a9dd9..74367cbed 100644 --- a/rpc/backend/tracing_test.go +++ b/rpc/backend/tracing_test.go @@ -117,7 +117,7 @@ func (suite *BackendTestSuite) TestTraceTransaction() { ) _, err := RegisterBlockMultipleTxs(client, height, []types.Tx{txBz, txBz2}) suite.Require().NoError(err) - RegisterTraceTransactionWithPredecessors(queryClient, msgEthereumTx, []*evmtypes.MsgEthereumTx{msgEthereumTx}) + RegisterTraceTransactionWithPredecessors(queryClient, msgEthereumTx, nil) RegisterConsensusParams(client, height) }, &types.Block{Header: types.Header{Height: 1, ChainID: ChainID}, Data: types.Data{Txs: []types.Tx{txBz, txBz2}}}, diff --git a/x/vm/keeper/gas.go b/x/vm/keeper/gas.go index da2c72f36..fe86baccc 100644 --- a/x/vm/keeper/gas.go +++ b/x/vm/keeper/gas.go @@ -25,21 +25,34 @@ func (k *Keeper) GetEthIntrinsicGas(ctx sdk.Context, msg core.Message, cfg *para return core.IntrinsicGas(msg.Data(), msg.AccessList(), isContractCreation, homestead, istanbul) } -// RefundGas transfers the leftover gas to the sender of the message, capped to half of the total gas +// RefundGas transfers the leftover gas and baseFee amount to the sender of the message, capped to half of the total gas // consumed in the transaction. Additionally, the function sets the total gas consumed to the value // returned by the EVM execution, thus ignoring the previous intrinsic gas consumed during in the // AnteHandler. -func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64, denom string) error { - // Return EVM tokens for remaining gas, exchanged at the original rate. +// and then burns baseFee amount from the sender account. +func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64, gasUsed uint64, baseFee *big.Int, denom string) error { + if msg.GasPrice().Sign() < 0 { + return errorsmod.Wrapf(types.ErrInvalidRefund, "gas price cannot be negative %d", msg.GasPrice().Int64()) + } + + // refundable amount for leftover gas: leftoverGas * effectiveGasPrice remaining := new(big.Int).Mul(new(big.Int).SetUint64(leftoverGas), msg.GasPrice()) - switch remaining.Sign() { + // refundable amount for base fee: baseFee * gasUsed + baseFeeRefund := big.NewInt(0) + if msg.GasPrice().Sign() > 0 && baseFee != nil && baseFee.Sign() > 0 && gasUsed > 0 { + baseFeeRefund = new(big.Int).Mul(baseFee, new(big.Int).SetUint64(gasUsed)) + } + + refundAmt := new(big.Int).Add(remaining, baseFeeRefund) + + switch refundAmt.Sign() { case -1: // negative refund errors - return errorsmod.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", remaining.Int64()) + return errorsmod.Wrapf(types.ErrInvalidRefund, "refunded amount value cannot be negative %d", refundAmt.Int64()) case 1: // positive amount refund - refundedCoins := sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(remaining))} + refundedCoins := sdk.Coins{sdk.NewCoin(denom, sdkmath.NewIntFromBigInt(refundAmt))} // refund to sender from the fee collector module account, which is the escrow account in charge of collecting tx fees err := k.bankWrapper.SendCoinsFromModuleToAccount(ctx, authtypes.FeeCollectorName, msg.From().Bytes(), refundedCoins) @@ -51,6 +64,13 @@ func (k *Keeper) RefundGas(ctx sdk.Context, msg core.Message, leftoverGas uint64 // no refund, consume gas and update the tx gas meter } + // burn baseFee * gasUsed from the sender account after refund + if baseFeeRefund.Sign() > 0 { + if err := k.bankWrapper.BurnAmountFromAccount(ctx, msg.From().Bytes(), baseFeeRefund); err != nil { + return errorsmod.Wrapf(err, "failed to burn base fee from sender %s", msg.From().Hex()) + } + } + return nil } diff --git a/x/vm/keeper/msg_server_test.go b/x/vm/keeper/msg_server_test.go index 1602945e1..2c452fef4 100644 --- a/x/vm/keeper/msg_server_test.go +++ b/x/vm/keeper/msg_server_test.go @@ -3,6 +3,9 @@ package keeper_test import ( "math/big" + ethparams "github.com/ethereum/go-ethereum/params" + + sdkmath "cosmossdk.io/math" "github.com/cosmos/evm/testutil/integration/os/utils" "github.com/cosmos/evm/x/vm/types" @@ -13,7 +16,11 @@ import ( func (suite *KeeperTestSuite) TestEthereumTx() { suite.enableFeemarket = true - defer func() { suite.enableFeemarket = false }() + suite.mintFeeCollector = true + defer func() { + suite.enableFeemarket = false + suite.mintFeeCollector = false + }() suite.SetupTest() testCases := []struct { name string @@ -53,6 +60,30 @@ func (suite *KeeperTestSuite) TestEthereumTx() { suite.Run(tc.name, func() { msg := tc.getMsg() + // Ensure fee collector has sufficient balance for each subtest + if suite.mintFeeCollector { + feeCollectorAddr := authtypes.NewModuleAddress(authtypes.FeeCollectorName) + denom := types.GetEVMCoinExtendedDenom() + currentBalance := suite.network.App.BankKeeper.GetBalance(suite.network.GetContext(), feeCollectorAddr, denom) + + baseFee := suite.network.App.EVMKeeper.GetBaseFee(suite.network.GetContext()) + if baseFee == nil { + baseFee = big.NewInt(0) + } + + gasLimit := new(big.Int).SetUint64(msg.GetGas()) + requiredBalance := sdkmath.NewIntFromBigInt(new(big.Int).Mul(gasLimit, baseFee)). + Add(sdkmath.NewIntFromUint64(ethparams.TxGas - 1)) + + if currentBalance.Amount.LT(requiredBalance) { + coinsToAdd := sdktypes.NewCoins(sdktypes.NewCoin(denom, requiredBalance.Sub(currentBalance.Amount))) + err := suite.network.App.BankKeeper.MintCoins(suite.network.GetContext(), types.ModuleName, coinsToAdd) + suite.Require().NoError(err) + err = suite.network.App.BankKeeper.SendCoinsFromModuleToModule(suite.network.GetContext(), types.ModuleName, authtypes.FeeCollectorName, coinsToAdd) + suite.Require().NoError(err) + } + } + // Function to be tested res, err := suite.network.App.EVMKeeper.EthereumTx(suite.network.GetContext(), msg) @@ -76,7 +107,6 @@ func (suite *KeeperTestSuite) TestEthereumTx() { suite.Require().NoError(err) }) } - suite.enableFeemarket = false } func (suite *KeeperTestSuite) TestUpdateParams() { diff --git a/x/vm/keeper/setup_test.go b/x/vm/keeper/setup_test.go index 762f9da5f..d9b86c4ed 100644 --- a/x/vm/keeper/setup_test.go +++ b/x/vm/keeper/setup_test.go @@ -4,7 +4,7 @@ import ( "math" "testing" - "github.com/ethereum/go-ethereum/params" + ethparams "github.com/ethereum/go-ethereum/params" "github.com/stretchr/testify/suite" "github.com/cosmos/evm/testutil/integration/os/factory" @@ -56,7 +56,7 @@ func (suite *KeeperTestSuite) SetupTest() { // Set custom balance based on test params customGenesis := network.CustomGenesisState{} feemarketGenesis := feemarkettypes.DefaultGenesisState() - if s.enableFeemarket { + if suite.enableFeemarket { feemarketGenesis.Params.EnableHeight = 1 feemarketGenesis.Params.NoBaseFee = false } else { @@ -64,9 +64,14 @@ func (suite *KeeperTestSuite) SetupTest() { } customGenesis[feemarkettypes.ModuleName] = feemarketGenesis - if s.mintFeeCollector { - // mint some coin to fee collector - coins := sdk.NewCoins(sdk.NewCoin(evmtypes.GetEVMCoinDenom(), sdkmath.NewInt(int64(params.TxGas)-1))) + if suite.mintFeeCollector { + // Mint coins to fee collector for gas refunds + baseFee := feemarketGenesis.Params.BaseFee.TruncateInt() + gasUsed := sdkmath.NewIntFromUint64(ethparams.TxGas) + refundBuffer := sdkmath.NewIntFromUint64(ethparams.TxGas - 1) + requiredBalance := gasUsed.Mul(baseFee).Add(refundBuffer) + + coins := sdk.NewCoins(sdk.NewCoin(evmtypes.GetEVMCoinExtendedDenom(), requiredBalance)) balances := []banktypes.Balance{ { Address: authtypes.NewModuleAddress(authtypes.FeeCollectorName).String(), @@ -85,13 +90,13 @@ func (suite *KeeperTestSuite) SetupTest() { gh := grpc.NewIntegrationHandler(nw) tf := factory.New(nw, gh) - s.network = nw - s.factory = tf - s.handler = gh - s.keyring = keys + suite.network = nw + suite.factory = tf + suite.handler = gh + suite.keyring = keys chainConfig := evmtypes.DefaultChainConfig(suite.network.GetChainID()) - if !s.enableLondonHF { + if !suite.enableLondonHF { maxInt := sdkmath.NewInt(math.MaxInt64) chainConfig.LondonBlock = &maxInt chainConfig.ArrowGlacierBlock = &maxInt diff --git a/x/vm/keeper/state_transition.go b/x/vm/keeper/state_transition.go index 665429a62..3085f6cdc 100644 --- a/x/vm/keeper/state_transition.go +++ b/x/vm/keeper/state_transition.go @@ -240,7 +240,7 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t evmDenom := types.GetEVMCoinDenom() // refund gas in order to match the Ethereum gas consumption instead of the default SDK one. - if err = k.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, evmDenom); err != nil { + if err = k.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, res.GasUsed, cfg.BaseFee, evmDenom); err != nil { return nil, errorsmod.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From()) } diff --git a/x/vm/keeper/state_transition_test.go b/x/vm/keeper/state_transition_test.go index e63fd1d46..afe0e5be6 100644 --- a/x/vm/keeper/state_transition_test.go +++ b/x/vm/keeper/state_transition_test.go @@ -460,11 +460,14 @@ func (suite *KeeperTestSuite) TestRefundGas() { gasUsed := transactionGas - tc.leftoverGas refund := keeper.GasToRefund(vmdb.GetRefund(), gasUsed, tc.refundQuotient) suite.Require().Equal(tc.expGasRefund, refund) + baseFee := big.NewInt(1) err = unitNetwork.App.EVMKeeper.RefundGas( unitNetwork.GetContext(), coreMsg, refund, + gasUsed, + baseFee, unitNetwork.GetBaseDenom(), )