Skip to content
3 changes: 2 additions & 1 deletion evmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"github.com/cosmos/evm/x/feemarket"
feemarketkeeper "github.com/cosmos/evm/x/feemarket/keeper"
feemarkettypes "github.com/cosmos/evm/x/feemarket/types"

Check failure on line 35 in evmd/app.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

File is not properly formatted (gci)
// 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"
Expand Down Expand Up @@ -154,7 +155,7 @@

// module account permissions
maccPerms = map[string][]string{
authtypes.FeeCollectorName: nil,
authtypes.FeeCollectorName: {authtypes.Burner},
distrtypes.ModuleName: nil,
ibctransfertypes.ModuleName: {authtypes.Minter, authtypes.Burner},
minttypes.ModuleName: {authtypes.Minter},
Expand Down
31 changes: 31 additions & 0 deletions rpc/backend/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,38 @@
ethMsg.Hash = additional.Hash.Hex()
ethMsg.From = additional.Sender.Hex()
return ethMsg
}

Check failure on line 339 in rpc/backend/blocks.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

File is not properly formatted (gofumpt)
func (b *Backend) parseDerivedTxFromAdditionalFieldsForTrace(
additional *rpctypes.TxResultAdditionalFields,
) *evmtypes.MsgEthereumTx {
recipient := additional.Recipient

// for transactions before v31 this value was mistakenly used for Gas field
gas := additional.GasUsed
if additional.GasLimit != nil {
gas = *additional.GasLimit
}
t := ethtypes.NewTx(&ethtypes.LegacyTx{
Nonce: additional.Nonce,
Data: additional.Data,
Gas: gas,
To: &recipient,
GasPrice: nil,
Value: additional.Value,
V: big.NewInt(0),
R: big.NewInt(0),
S: big.NewInt(0),
})
ethMsg := &evmtypes.MsgEthereumTx{}
err := ethMsg.FromEthereumTx(t)
if err != nil {
b.logger.Error("can not create eth msg", err.Error())
return nil
}
ethMsg.Hash = additional.Hash.Hex()
ethMsg.From = additional.Sender.Hex()
return ethMsg
}

// HeaderByNumber returns the block header identified by height.
func (b *Backend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) {
Expand Down
77 changes: 61 additions & 16 deletions rpc/backend/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// and returns them as a JSON object.
func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
// Get transaction by hash
transaction, _, err := b.GetTxByEthHash(hash)
transaction, additional, err := b.GetTxByEthHash(hash)
if err != nil {
b.logger.Debug("tx not found", "hash", hash)
return nil, err
Expand Down Expand Up @@ -49,19 +49,39 @@
}

var predecessors []*evmtypes.MsgEthereumTx
for _, txBz := range blk.Block.Txs[:transaction.TxIndex] {
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
for i := 0; i < int(transaction.TxIndex); i++ {
_, txAdditional, err := b.GetTxByTxIndex(blk.Block.Height, uint(i))

Check failure on line 53 in rpc/backend/tracing.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

G115: integer overflow conversion int -> uint (gosec)
if err != nil {
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
b.logger.Debug("failed to get tx by index",
"height", blk.Block.Height,
"index", i,
"error", err.Error())
continue
}
for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
continue

if txAdditional != nil {
// Handle synthetic EVM transaction
ethMsg := b.parseDerivedTxFromAdditionalFieldsForTrace(txAdditional)
if ethMsg != nil {
predecessors = append(predecessors, ethMsg)
}
continue
}

predecessors = append(predecessors, ethMsg)
// Fallback: decode as normal Cosmos tx
tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[i])
if err != nil {
b.logger.Debug("failed to decode transaction in block",
"height", blk.Block.Height,
"index", i,
"error", err.Error())
continue
}

for _, msg := range tx.GetMsgs() {
if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
predecessors = append(predecessors, ethMsg)
}
}
}

Expand All @@ -73,18 +93,43 @@

// add predecessor messages in current cosmos tx
index := int(transaction.MsgIndex) // #nosec G115

for i := 0; i < index; i++ {
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
if !ok {
msg := tx.GetMsgs()[i]
// Check if it’s a normal Ethereum tx
if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
predecessors = append(predecessors, ethMsg)
continue
}
predecessors = append(predecessors, ethMsg)
// Fetch additional data for predecessors
_, txAdditional, err := b.GetTxByEthHashAndMsgIndex(hash, i)
if err != nil {
b.logger.Debug("failed to get tx additional info", "error", err.Error())
continue
}

if txAdditional != nil {
ethMsg := b.parseDerivedTxFromAdditionalFieldsForTrace(txAdditional)
if ethMsg != nil {
predecessors = append(predecessors, ethMsg)
}
}
}
var ethMessage *evmtypes.MsgEthereumTx
var ok bool

ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
return nil, fmt.Errorf("invalid transaction type %T", tx)
if additional == nil {
ethMessage, ok = tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx.GetMsgs()[transaction.MsgIndex]))
return nil, fmt.Errorf("invalid transaction type %T", tx.GetMsgs()[transaction.MsgIndex])
}
} else {
ethMessage = b.parseDerivedTxFromAdditionalFieldsForTrace(additional)
if ethMessage == nil {
b.logger.Error("failed to get derived eth msg from additional fields")
return nil, fmt.Errorf("failed to get derived eth msg from additional fields")
}
}

nc, ok := b.clientCtx.Client.(tmrpcclient.NetworkClient)
Expand Down
20 changes: 20 additions & 0 deletions rpc/backend/tx_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
if i > math.MaxInt32 {
return nil, errors.New("tx index overflow")
}
res.EthTxIndex = int32(i)

Check failure on line 75 in rpc/backend/tx_info.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

G115: integer overflow conversion int -> int32 (gosec)
break
}
}
Expand Down Expand Up @@ -224,7 +224,7 @@
}

var from common.Address
if additional != nil {

Check failure on line 227 in rpc/backend/tx_info.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ifElseChain: rewrite if-else to switch statement (gocritic)
from = common.HexToAddress(ethMsg.From)
} else if ethMsg.Data != nil {
chainID, err := b.ChainID()
Expand Down Expand Up @@ -430,6 +430,26 @@
return txResult, txAdditional, nil
}

func (b *Backend) GetTxByEthHashAndMsgIndex(hash common.Hash, index int) (*types.TxResult, *rpctypes.TxResultAdditionalFields, error) {
if b.indexer != nil {
txRes, err := b.indexer.GetByTxHash(hash)
if err != nil {
return nil, nil, err
}
return txRes, nil, nil
}

// fallback to tendermint tx indexer
query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
txResult, txAdditional, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx {
return txs.GetTxByMsgIndex(index)
})
if err != nil {
return nil, nil, errorsmod.Wrapf(err, "GetTxByEthHash %s", hash.Hex())
}
return txResult, txAdditional, nil
}

// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
func (b *Backend) GetTxByTxIndex(height int64, index uint) (*types.TxResult, *rpctypes.TxResultAdditionalFields, error) {
int32Index := int32(index) //#nosec G115 -- checked for int overflow already
Expand Down
10 changes: 10 additions & 0 deletions rpc/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
Recipient common.Address
Sender common.Address
Nonce uint64
GasLimit uint64
Data []byte
}

Expand Down Expand Up @@ -153,7 +154,7 @@

// Handle fallback GasUsed
if len(p.Txs) == 1 && p.Txs[0].Type != evmtypes.DerivedTxType {
p.Txs[0].GasUsed = uint64(result.GasUsed)

Check failure on line 157 in rpc/types/events.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

G115: integer overflow conversion int64 -> uint64 (gosec)
}

// Handle failure fallback
Expand Down Expand Up @@ -233,6 +234,7 @@
GasUsed: parsedTx.GasUsed,
Data: parsedTx.Data,
Nonce: parsedTx.Nonce,
GasLimit: &parsedTx.GasLimit,
}, nil
}
return &types.TxResult{
Expand Down Expand Up @@ -285,6 +287,7 @@
GasUsed: parsedTx.GasUsed,
Data: parsedTx.Data,
Nonce: parsedTx.Nonce,
GasLimit: &parsedTx.GasLimit,
}, nil
}
return &types.TxResult{
Expand Down Expand Up @@ -411,6 +414,13 @@
}
tx.Nonce = nonce

case evmtypes.AttributeKeyTxGasLimit:
gasLimit, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
tx.GasLimit = gasLimit

case evmtypes.AttributeKeyTxData:
hexBytes, err := hexutil.Decode(value)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions rpc/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type TxResultAdditionalFields struct {
GasUsed uint64 `json:"gasUsed"`
Nonce uint64 `json:"nonce"`
Data []byte `json:"data"`
GasLimit *uint64 `json:"gasLimit"`
}

// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
Expand Down
1 change: 1 addition & 0 deletions x/vm/keeper/call_evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ func (k Keeper) DerivedEVMCallWithData(
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxData, hexutil.Encode(msg.Data())))
// adding nonce for more info in rpc methods in order to parse derived txs
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxNonce, strconv.FormatUint(nonce, 10)))
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxGasLimit, strconv.FormatUint(gasCap, 10)))
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
Expand Down
58 changes: 50 additions & 8 deletions x/vm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -487,10 +488,18 @@

for i, tx := range req.Predecessors {
ethTx := tx.AsTransaction()
msg, err := ethTx.AsMessage(signer, cfg.BaseFee)
if err != nil {
continue

var msg ethtypes.Message
// if tx is not unsigned, from field should be derived from signer, which can be done using AsMessage function
if !isUnsigned(ethTx) {
msg, err = ethTx.AsMessage(signer, cfg.BaseFee)
if err != nil {
continue
}
} else {
msg = unsignedTxAsMessage(common.BytesToAddress(req.Msg.GetFrom()), ethTx, cfg.BaseFee)
}

txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i) //nolint:gosec // G115 // won't exceed uint64
// reset gas meter for each transaction
Expand All @@ -515,7 +524,7 @@
_ = json.Unmarshal([]byte(req.TraceConfig.TracerJsonConfig), &tracerConfig)
}

result, _, err := k.traceTx(ctx, cfg, txConfig, signer, tx, req.TraceConfig, false, tracerConfig)
result, _, err := k.traceTx(ctx, cfg, txConfig, signer, common.BytesToAddress(req.Msg.GetFrom()), tx, req.TraceConfig, false, tracerConfig)
if err != nil {
// error will be returned with detail status from traceTx
return nil, err
Expand Down Expand Up @@ -582,7 +591,7 @@
ethTx := tx.AsTransaction()
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i) //nolint:gosec // G115 // won't exceed uint64
traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, ethTx, req.TraceConfig, true, nil)
traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, common.BytesToAddress(tx.GetFrom()), ethTx, req.TraceConfig, true, nil)
if err != nil {
result.Error = err.Error()
} else {
Expand All @@ -608,6 +617,7 @@
cfg *statedb.EVMConfig,
txConfig statedb.TxConfig,
signer ethtypes.Signer,
from common.Address,
tx *ethtypes.Transaction,
traceConfig *types.TraceConfig,
commitMessage bool,
Expand All @@ -620,9 +630,15 @@
err error
timeout = defaultTraceTimeout
)
msg, err := tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
var msg ethtypes.Message
// if tx is not unsigned, from field should be derived from signer, which can be done using AsMessage function
if !isUnsigned(tx) {
msg, err = tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
}
} else {
msg = unsignedTxAsMessage(from, tx, cfg.BaseFee)
}

if traceConfig == nil {
Expand Down Expand Up @@ -722,3 +738,29 @@

return &types.QueryConfigResponse{Config: config}, nil
}

func isUnsigned(ethTx *ethtypes.Transaction) bool {
r, v, s := ethTx.RawSignatureValues()

return (r == nil && v == nil && s == nil) || (r.Int64() == 0 && v.Int64() == 0 && s.Int64() == 0)
}

Check failure on line 746 in x/vm/keeper/grpc_query.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

File is not properly formatted (gofumpt)
func unsignedTxAsMessage(fromAddress common.Address, tx *ethtypes.Transaction, baseFee *big.Int) ethtypes.Message {
gasPrice := new(big.Int).Set(tx.GasPrice())
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
gasPrice = math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
}
return ethtypes.NewMessage(
fromAddress,
tx.To(),
tx.Nonce(),
tx.Value(),
tx.Gas(),
gasPrice,
tx.GasFeeCap(),
tx.GasTipCap(),
tx.Data(),
tx.AccessList(),
false,
)
}
23 changes: 23 additions & 0 deletions x/vm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
"github.com/cosmos/evm/x/vm/types"

errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"

Check failure on line 20 in x/vm/keeper/state_transition.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ST1019: package "cosmossdk.io/math" is being imported more than once (stylecheck)
sdkmath "cosmossdk.io/math"

Check failure on line 21 in x/vm/keeper/state_transition.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ST1019(related information): other import of "cosmossdk.io/math" (stylecheck)

sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
)

// NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters
Expand Down Expand Up @@ -243,6 +245,27 @@
if err = k.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, evmDenom); err != nil {
return nil, errorsmod.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From())
}
// burn base fee (EIP-1559): baseFee * gasUsed
if cfg.BaseFee != nil && cfg.BaseFee.Sign() > 0 && res.GasUsed > 0 {
// compute amount = baseFee * gasUsed
burnAmt := new(big.Int).Mul(cfg.BaseFee, new(big.Int).SetUint64(res.GasUsed))

// create coin in EVM denom
burnCoin := sdk.Coin{Denom: types.GetEVMCoinDenom(), Amount: sdkmath.NewIntFromBigInt(burnAmt)}

// convert to extended denom (chain denom) and burn from fee collector
converted, err := types.ConvertEvmCoinDenomToExtendedDenom(burnCoin)
if err != nil {
// handle error (log and/or return). If you return, it may fail the tx; choose behavior.
k.Logger(ctx).Error("failed to convert evm denom for base fee burn", "err", err)
} else {
burnCoins := sdk.Coins{converted}
if err := k.bankWrapper.BurnCoins(ctx, authtypes.FeeCollectorName, burnCoins); err != nil {
// handle error - either log or return error. Logging avoids failing block.
k.Logger(ctx).Error("failed to burn base fee from fee collector", "err", err)
}
}
}

if len(logs) > 0 {
// Update transient block bloom filter
Expand Down
1 change: 1 addition & 0 deletions x/vm/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
AttributeKeyTxType = "txType"
AttributeKeyTxLog = "txLog"
AttributeKeyTxNonce = "txNonce"
AttributeKeyTxGasLimit = "txGasLimit"
AttributeKeyTxData = "txData"

// tx failed in eth vm execution
Expand Down
Loading